You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3428 lines
92 KiB
3428 lines
92 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "soundenvelope.h" |
|
#include "npc_manhack.h" |
|
#include "ai_default.h" |
|
#include "ai_node.h" |
|
#include "ai_navigator.h" |
|
#include "ai_pathfinder.h" |
|
#include "ai_moveprobe.h" |
|
#include "ai_memory.h" |
|
#include "ai_squad.h" |
|
#include "ai_route.h" |
|
#include "explode.h" |
|
#include "basegrenade_shared.h" |
|
#include "ndebugoverlay.h" |
|
#include "decals.h" |
|
#include "gib.h" |
|
#include "game.h" |
|
#include "ai_interactions.h" |
|
#include "IEffects.h" |
|
#include "vstdlib/random.h" |
|
#include "engine/IEngineSound.h" |
|
#include "movevars_shared.h" |
|
#include "npcevent.h" |
|
#include "props.h" |
|
#include "te_effect_dispatch.h" |
|
#include "ai_squadslot.h" |
|
#include "world.h" |
|
#include "smoke_trail.h" |
|
#include "func_break.h" |
|
#include "physics_impact_damage.h" |
|
#include "weapon_physcannon.h" |
|
#include "physics_prop_ragdoll.h" |
|
#include "soundent.h" |
|
#include "ammodef.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
// When the engine is running and the manhack is operating under power |
|
// we don't let gravity affect him. |
|
#define MANHACK_GRAVITY 0.000 |
|
|
|
#define MANHACK_GIB_COUNT 5 |
|
#define MANHACK_INGORE_WATER_DIST 384 |
|
|
|
// Sound stuff |
|
#define MANHACK_PITCH_DIST1 512 |
|
#define MANHACK_MIN_PITCH1 (100) |
|
#define MANHACK_MAX_PITCH1 (160) |
|
#define MANHACK_WATER_PITCH1 (85) |
|
#define MANHACK_VOLUME1 0.55 |
|
|
|
#define MANHACK_PITCH_DIST2 400 |
|
#define MANHACK_MIN_PITCH2 (85) |
|
#define MANHACK_MAX_PITCH2 (190) |
|
#define MANHACK_WATER_PITCH2 (90) |
|
|
|
#define MANHACK_NOISEMOD_HIDE 5000 |
|
|
|
#define MANHACK_BODYGROUP_BLADE 1 |
|
#define MANHACK_BODYGROUP_BLUR 2 |
|
#define MANHACK_BODYGROUP_OFF 0 |
|
#define MANHACK_BODYGROUP_ON 1 |
|
|
|
// ANIMATION EVENTS |
|
#define MANHACK_AE_START_ENGINE 50 |
|
#define MANHACK_AE_DONE_UNPACKING 51 |
|
#define MANHACK_AE_OPEN_BLADE 52 |
|
|
|
//#define MANHACK_GLOW_SPRITE "sprites/laserdot.vmt" |
|
#define MANHACK_GLOW_SPRITE "sprites/glow1.vmt" |
|
|
|
#define MANHACK_CHARGE_MIN_DIST 200 |
|
|
|
ConVar sk_manhack_health( "sk_manhack_health","0"); |
|
ConVar sk_manhack_melee_dmg( "sk_manhack_melee_dmg","0"); |
|
ConVar sk_manhack_v2( "sk_manhack_v2","1"); |
|
|
|
extern void SpawnBlood(Vector vecSpot, const Vector &vAttackDir, int bloodColor, float flDamage); |
|
extern float GetFloorZ(const Vector &origin); |
|
|
|
//----------------------------------------------------------------------------- |
|
// Private activities. |
|
//----------------------------------------------------------------------------- |
|
Activity ACT_MANHACK_UNPACK; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Manhack Conditions |
|
//----------------------------------------------------------------------------- |
|
enum ManhackConditions |
|
{ |
|
COND_MANHACK_START_ATTACK = LAST_SHARED_CONDITION, // We are able to do a bombing run on the current enemy. |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Manhack schedules. |
|
//----------------------------------------------------------------------------- |
|
enum ManhackSchedules |
|
{ |
|
SCHED_MANHACK_ATTACK_HOVER = LAST_SHARED_SCHEDULE, |
|
SCHED_MANHACK_DEPLOY, |
|
SCHED_MANHACK_REGROUP, |
|
SCHED_MANHACK_SWARM_IDLE, |
|
SCHED_MANHACK_SWARM, |
|
SCHED_MANHACK_SWARM_FAILURE, |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Manhack tasks. |
|
//----------------------------------------------------------------------------- |
|
enum ManhackTasks |
|
{ |
|
TASK_MANHACK_HOVER = LAST_SHARED_TASK, |
|
TASK_MANHACK_UNPACK, |
|
TASK_MANHACK_FIND_SQUAD_CENTER, |
|
TASK_MANHACK_FIND_SQUAD_MEMBER, |
|
TASK_MANHACK_MOVEAT_SAVEPOSITION, |
|
}; |
|
|
|
BEGIN_DATADESC( CNPC_Manhack ) |
|
|
|
DEFINE_FIELD( m_vForceVelocity, FIELD_VECTOR), |
|
|
|
DEFINE_FIELD( m_vTargetBanking, FIELD_VECTOR), |
|
DEFINE_FIELD( m_vForceMoveTarget, FIELD_POSITION_VECTOR), |
|
DEFINE_FIELD( m_fForceMoveTime, FIELD_TIME), |
|
DEFINE_FIELD( m_vSwarmMoveTarget, FIELD_POSITION_VECTOR), |
|
DEFINE_FIELD( m_fSwarmMoveTime, FIELD_TIME), |
|
DEFINE_FIELD( m_fEnginePowerScale, FIELD_FLOAT), |
|
|
|
DEFINE_FIELD( m_flNextEngineSoundTime, FIELD_TIME), |
|
DEFINE_FIELD( m_flEngineStallTime, FIELD_TIME), |
|
DEFINE_FIELD( m_flNextBurstTime, FIELD_TIME ), |
|
DEFINE_FIELD( m_flWaterSuspendTime, FIELD_TIME), |
|
DEFINE_FIELD( m_nLastSpinSound, FIELD_INTEGER ), |
|
|
|
// Death |
|
DEFINE_FIELD( m_fSparkTime, FIELD_TIME), |
|
DEFINE_FIELD( m_fSmokeTime, FIELD_TIME), |
|
|
|
DEFINE_FIELD( m_bDirtyPitch, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_bGib, FIELD_BOOLEAN), |
|
DEFINE_FIELD( m_bHeld, FIELD_BOOLEAN), |
|
|
|
DEFINE_FIELD( m_bHackedByAlyx, FIELD_BOOLEAN), |
|
DEFINE_FIELD( m_vecLoiterPosition, FIELD_POSITION_VECTOR), |
|
DEFINE_FIELD( m_fTimeNextLoiterPulse, FIELD_TIME), |
|
|
|
DEFINE_FIELD( m_flBumpSuppressTime, FIELD_TIME ), |
|
|
|
DEFINE_FIELD( m_bBladesActive, FIELD_BOOLEAN), |
|
DEFINE_FIELD( m_flBladeSpeed, FIELD_FLOAT), |
|
DEFINE_KEYFIELD( m_bIgnoreClipbrushes, FIELD_BOOLEAN, "ignoreclipbrushes" ), |
|
DEFINE_FIELD( m_hSmokeTrail, FIELD_EHANDLE), |
|
|
|
// DEFINE_FIELD( m_pLightGlow, FIELD_CLASSPTR ), |
|
// DEFINE_FIELD( m_pEyeGlow, FIELD_CLASSPTR ), |
|
|
|
DEFINE_FIELD( m_iPanel1, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iPanel2, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iPanel3, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_iPanel4, FIELD_INTEGER ), |
|
|
|
DEFINE_FIELD( m_nLastWaterLevel, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_bDoSwarmBehavior, FIELD_BOOLEAN ), |
|
|
|
DEFINE_FIELD( m_nEnginePitch1, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_flEnginePitch1Time, FIELD_TIME ), |
|
DEFINE_FIELD( m_nEnginePitch2, FIELD_INTEGER ), |
|
DEFINE_FIELD( m_flEnginePitch2Time, FIELD_TIME ), |
|
|
|
// Physics Influence |
|
DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ), |
|
DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ), |
|
|
|
DEFINE_FIELD( m_flBurstDuration, FIELD_FLOAT ), |
|
DEFINE_FIELD( m_vecBurstDirection, FIELD_VECTOR ), |
|
DEFINE_FIELD( m_bShowingHostile, FIELD_BOOLEAN ), |
|
|
|
// Function Pointers |
|
DEFINE_INPUTFUNC( FIELD_VOID, "DisableSwarm", InputDisableSwarm ), |
|
DEFINE_INPUTFUNC( FIELD_VOID, "Unpack", InputUnpack ), |
|
|
|
DEFINE_ENTITYFUNC( CrashTouch ), |
|
|
|
DEFINE_BASENPCINTERACTABLE_DATADESC(), |
|
|
|
END_DATADESC() |
|
|
|
|
|
LINK_ENTITY_TO_CLASS( npc_manhack, CNPC_Manhack ); |
|
|
|
IMPLEMENT_SERVERCLASS_ST(CNPC_Manhack, DT_NPC_Manhack) |
|
SendPropIntWithMinusOneFlag (SENDINFO(m_nEnginePitch1), 8 ), |
|
SendPropFloat(SENDINFO(m_flEnginePitch1Time), 0, SPROP_NOSCALE), |
|
SendPropIntWithMinusOneFlag(SENDINFO(m_nEnginePitch2), 8 ) |
|
END_SEND_TABLE() |
|
|
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
CNPC_Manhack::CNPC_Manhack() |
|
{ |
|
#ifdef _DEBUG |
|
m_vForceMoveTarget.Init(); |
|
m_vSwarmMoveTarget.Init(); |
|
m_vTargetBanking.Init(); |
|
m_vForceVelocity.Init(); |
|
#endif |
|
m_bDirtyPitch = true; |
|
m_nLastWaterLevel = 0; |
|
m_nEnginePitch1 = -1; |
|
m_nEnginePitch2 = -1; |
|
m_flEnginePitch1Time = 0; |
|
m_flEnginePitch1Time = 0; |
|
m_bDoSwarmBehavior = true; |
|
m_flBumpSuppressTime = 0; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
CNPC_Manhack::~CNPC_Manhack() |
|
{ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Indicates this NPC's place in the relationship table. |
|
//----------------------------------------------------------------------------- |
|
Class_T CNPC_Manhack::Classify(void) |
|
{ |
|
return (m_bHeld||m_bHackedByAlyx) ? CLASS_PLAYER_ALLY : CLASS_MANHACK; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Turns the manhack into a physics corpse when dying. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::Event_Dying(void) |
|
{ |
|
DestroySmokeTrail(); |
|
SetHullSizeNormal(); |
|
BaseClass::Event_Dying(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::GatherConditions() |
|
{ |
|
BaseClass::GatherConditions(); |
|
|
|
if( IsLoitering() && GetEnemy() ) |
|
{ |
|
StopLoitering(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::PrescheduleThink( void ) |
|
{ |
|
BaseClass::PrescheduleThink(); |
|
|
|
UpdatePanels(); |
|
|
|
if( m_flWaterSuspendTime > gpGlobals->curtime ) |
|
{ |
|
// Stuck in water! |
|
|
|
// Reduce engine power so that the manhack lifts out of the water slowly. |
|
m_fEnginePowerScale = 0.75; |
|
} |
|
|
|
// ---------------------------------------- |
|
// Am I in water? |
|
// ---------------------------------------- |
|
if ( GetWaterLevel() > 0 ) |
|
{ |
|
if( m_nLastWaterLevel == 0 ) |
|
{ |
|
Splash( WorldSpaceCenter() ); |
|
} |
|
|
|
if( IsAlive() ) |
|
{ |
|
// If I've been out of water for 2 seconds or more, I'm eligible to be stuck in water again. |
|
if( gpGlobals->curtime - m_flWaterSuspendTime > 2.0 ) |
|
{ |
|
m_flWaterSuspendTime = gpGlobals->curtime + 1.0; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
if( m_nLastWaterLevel != 0 ) |
|
{ |
|
Splash( WorldSpaceCenter() ); |
|
} |
|
} |
|
|
|
m_nLastWaterLevel = GetWaterLevel(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::TraceAttack( const CTakeDamageInfo &info, const Vector &vecDir, trace_t *ptr, CDmgAccumulator *pAccumulator ) |
|
{ |
|
g_vecAttackDir = vecDir; |
|
|
|
if ( info.GetDamageType() & DMG_BULLET) |
|
{ |
|
g_pEffects->Ricochet(ptr->endpos,ptr->plane.normal); |
|
} |
|
|
|
if ( info.GetDamageType() & DMG_CLUB ) |
|
{ |
|
// Clubbed! |
|
// UTIL_Smoke(GetAbsOrigin(), random->RandomInt(10, 15), 10); |
|
g_pEffects->Sparks( ptr->endpos, 1, 1, &ptr->plane.normal ); |
|
} |
|
|
|
BaseClass::TraceAttack( info, vecDir, ptr, pAccumulator ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::DeathSound( const CTakeDamageInfo &info ) |
|
{ |
|
StopSound("NPC_Manhack.Stunned"); |
|
CPASAttenuationFilter filter2( this, "NPC_Manhack.Die" ); |
|
EmitSound( filter2, entindex(), "NPC_Manhack.Die" ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_Manhack::ShouldGib( const CTakeDamageInfo &info ) |
|
{ |
|
return ( m_bGib ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::Event_Killed( const CTakeDamageInfo &info ) |
|
{ |
|
// turn off the blur! |
|
SetBodygroup( MANHACK_BODYGROUP_BLUR, MANHACK_BODYGROUP_OFF ); |
|
|
|
// Sparks |
|
for (int i = 0; i < 3; 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, 2 ); |
|
} |
|
|
|
// Light |
|
CBroadcastRecipientFilter filter; |
|
te->DynamicLight( filter, 0.0, &GetAbsOrigin(), 255, 180, 100, 0, 100, 0.1, 0 ); |
|
|
|
if ( m_nEnginePitch1 < 0 ) |
|
{ |
|
// Probably this manhack was killed immediately after spawning. Turn the sound |
|
// on right now so that we can pitch it up for the crash! |
|
SoundInit(); |
|
} |
|
|
|
// Always gib when clubbed or blasted or crushed, or just randomly |
|
if ( ( info.GetDamageType() & (DMG_CLUB|DMG_CRUSH|DMG_BLAST) ) || ( random->RandomInt( 0, 1 ) ) ) |
|
{ |
|
m_bGib = true; |
|
} |
|
else |
|
{ |
|
m_bGib = false; |
|
|
|
//FIXME: These don't stay with the ragdolls currently -- jdw |
|
// Long fadeout on the sprites!! |
|
KillSprites( 0.0f ); |
|
} |
|
|
|
BaseClass::Event_Killed( info ); |
|
} |
|
|
|
void CNPC_Manhack::HitPhysicsObject( CBaseEntity *pOther ) |
|
{ |
|
IPhysicsObject *pOtherPhysics = pOther->VPhysicsGetObject(); |
|
Vector pos, posOther; |
|
// Put the force on the line between the manhack origin and hit object origin |
|
VPhysicsGetObject()->GetPosition( &pos, NULL ); |
|
pOtherPhysics->GetPosition( &posOther, NULL ); |
|
Vector dir = posOther - pos; |
|
VectorNormalize(dir); |
|
// size/2 is approx radius |
|
pos += dir * WorldAlignSize().x * 0.5; |
|
Vector cross; |
|
|
|
// UNDONE: Use actual manhack up vector so the fake blade is |
|
// in the right plane? |
|
// Get a vector in the x/y plane in the direction of blade spin (clockwise) |
|
CrossProduct( dir, Vector(0,0,1), cross ); |
|
VectorNormalize( cross ); |
|
// force is a 30kg object going 100 in/s |
|
pOtherPhysics->ApplyForceOffset( cross * 30 * 100, pos ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Take damage from being thrown by a physcannon |
|
//----------------------------------------------------------------------------- |
|
#define MANHACK_SMASH_SPEED 500.0 // How fast a manhack must slam into something to take full damage |
|
void CNPC_Manhack::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 / MANHACK_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 a vehicle; it's like a really big crowbar |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::TakeDamageFromVehicle( int index, gamevcollisionevent_t *pEvent ) |
|
{ |
|
// Use the vehicle velocity to determine the damage |
|
int otherIndex = !index; |
|
CBaseEntity *pOther = pEvent->pEntities[otherIndex]; |
|
|
|
float flSpeed = pEvent->preVelocity[ otherIndex ].Length(); |
|
flSpeed = clamp( flSpeed, 300.0f, 600.0f ); |
|
float flDamage = SimpleSplineRemapVal( flSpeed, 300.0f, 600.0f, 0.0f, 1.0f ); |
|
if ( flDamage == 0.0f ) |
|
return; |
|
|
|
flDamage *= 20.0f; |
|
|
|
Vector damagePos; |
|
pEvent->pInternalData->GetContactPoint( damagePos ); |
|
|
|
Vector damageForce = 2.0f * pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass(); |
|
if ( damageForce == vec3_origin ) |
|
{ |
|
// This can happen if this entity is a func_breakable, and can't move. |
|
// Use the velocity of the entity that hit us instead. |
|
damageForce = 2.0f * pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass(); |
|
} |
|
Assert( damageForce != vec3_origin ); |
|
CTakeDamageInfo dmgInfo( pOther, pOther, damageForce, damagePos, flDamage, DMG_CRUSH ); |
|
TakeDamage( dmgInfo ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Take damage from combine ball |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::TakeDamageFromPhysicsImpact( int index, gamevcollisionevent_t *pEvent ) |
|
{ |
|
CBaseEntity *pHitEntity = pEvent->pEntities[!index]; |
|
|
|
// NOTE: Bypass the normal impact energy scale here. |
|
float flDamageScale = PlayerHasMegaPhysCannon() ? 10.0f : 1.0f; |
|
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 ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
#define MANHACK_SMASH_TIME 0.35 // How long after being thrown from a physcannon that a manhack is eligible to die from impact |
|
void CNPC_Manhack::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent ) |
|
{ |
|
BaseClass::VPhysicsCollision( index, pEvent ); |
|
|
|
// Take no impact damage while being carried. |
|
if ( IsHeldByPhyscannon() ) |
|
return; |
|
|
|
// Wake us up |
|
if ( m_spawnflags & SF_MANHACK_PACKED_UP ) |
|
{ |
|
SetCondition( COND_LIGHT_DAMAGE ); |
|
} |
|
|
|
int otherIndex = !index; |
|
CBaseEntity *pHitEntity = pEvent->pEntities[otherIndex]; |
|
|
|
CBasePlayer *pPlayer = HasPhysicsAttacker( MANHACK_SMASH_TIME ); |
|
if( pPlayer ) |
|
{ |
|
if (!pHitEntity) |
|
{ |
|
TakeDamageFromPhyscannon( pPlayer ); |
|
StopBurst( true ); |
|
return; |
|
} |
|
|
|
// Don't take damage from NPCs or server ragdolls killed by the manhack |
|
CRagdollProp *pRagdollProp = dynamic_cast<CRagdollProp*>(pHitEntity); |
|
if (!pHitEntity->IsNPC() && (!pRagdollProp || pRagdollProp->GetKiller() != this)) |
|
{ |
|
TakeDamageFromPhyscannon( pPlayer ); |
|
StopBurst( true ); |
|
return; |
|
} |
|
} |
|
|
|
if ( pHitEntity ) |
|
{ |
|
// It can take physics damage if it rams into a vehicle |
|
if ( pHitEntity->GetServerVehicle() ) |
|
{ |
|
TakeDamageFromVehicle( index, pEvent ); |
|
} |
|
else if ( pHitEntity->HasPhysicsAttacker( 0.5f ) ) |
|
{ |
|
// It also 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 ); |
|
} |
|
else if ( m_iHealth <= 0 ) |
|
{ |
|
TakeDamageFromPhysicsImpact( index, pEvent ); |
|
} |
|
|
|
StopBurst( true ); |
|
} |
|
} |
|
|
|
|
|
void CNPC_Manhack::VPhysicsShadowCollision( int index, gamevcollisionevent_t *pEvent ) |
|
{ |
|
int otherIndex = !index; |
|
CBaseEntity *pOther = pEvent->pEntities[otherIndex]; |
|
|
|
if ( pOther->GetMoveType() == MOVETYPE_VPHYSICS ) |
|
{ |
|
HitPhysicsObject( pOther ); |
|
} |
|
BaseClass::VPhysicsShadowCollision( index, pEvent ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Manhack is out of control! (dying) Just explode as soon as you touch anything! |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::CrashTouch( CBaseEntity *pOther ) |
|
{ |
|
CTakeDamageInfo info( GetWorldEntity(), GetWorldEntity(), 25, DMG_CRUSH ); |
|
|
|
CorpseGib( info ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Create smoke trail! |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::CreateSmokeTrail() |
|
{ |
|
if ( HasSpawnFlags( SF_MANHACK_NO_DAMAGE_EFFECTS ) ) |
|
return; |
|
|
|
if ( m_hSmokeTrail != NULL ) |
|
return; |
|
|
|
SmokeTrail *pSmokeTrail = SmokeTrail::CreateSmokeTrail(); |
|
if( !pSmokeTrail ) |
|
return; |
|
|
|
pSmokeTrail->m_SpawnRate = 20; |
|
pSmokeTrail->m_ParticleLifetime = 0.5f; |
|
pSmokeTrail->m_StartSize = 8; |
|
pSmokeTrail->m_EndSize = 32; |
|
pSmokeTrail->m_SpawnRadius = 5; |
|
pSmokeTrail->m_MinSpeed = 15; |
|
pSmokeTrail->m_MaxSpeed = 25; |
|
|
|
pSmokeTrail->m_StartColor.Init( 0.4f, 0.4f, 0.4f ); |
|
pSmokeTrail->m_EndColor.Init( 0, 0, 0 ); |
|
|
|
pSmokeTrail->SetLifetime(-1); |
|
pSmokeTrail->FollowEntity(this); |
|
|
|
m_hSmokeTrail = pSmokeTrail; |
|
} |
|
|
|
void CNPC_Manhack::DestroySmokeTrail() |
|
{ |
|
if ( m_hSmokeTrail.Get() ) |
|
{ |
|
UTIL_Remove( m_hSmokeTrail ); |
|
m_hSmokeTrail = NULL; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
int CNPC_Manhack::OnTakeDamage_Alive( const CTakeDamageInfo &info ) |
|
{ |
|
// Hafta make a copy of info cause we might need to scale damage.(sjb) |
|
CTakeDamageInfo tdInfo = info; |
|
|
|
if( tdInfo.GetAmmoType() == GetAmmoDef()->Index("SniperRound") ) |
|
{ |
|
// Unfortunately, this is the easiest way to stop the sniper killing manhacks in one shot. |
|
tdInfo.SetDamage( m_iMaxHealth>>1 ); |
|
} |
|
|
|
if (info.GetDamageType() & DMG_PHYSGUN ) |
|
{ |
|
m_flBladeSpeed = 20.0; |
|
|
|
// respond to physics |
|
// FIXME: shouldn't this happen in a base class? Anyway to prevent it from happening twice? |
|
VPhysicsTakeDamage( info ); |
|
|
|
// reduce damage to nothing |
|
tdInfo.SetDamage( 1.0 ); |
|
|
|
StopBurst( true ); |
|
} |
|
else if ( info.GetDamageType() & DMG_AIRBOAT ) |
|
{ |
|
// Airboat gun kills me instantly. |
|
tdInfo.SetDamage( GetHealth() ); |
|
} |
|
else if (info.GetDamageType() & DMG_CLUB) |
|
{ |
|
// Being hit by a club means a couple of things: |
|
// |
|
// -I'm going to be knocked away from the person that clubbed me. |
|
// if fudging this vector a little bit could help me slam into a physics object, |
|
// then make that adjustment. This is a simple heuristic. The manhack will be |
|
// directed towards the physics object that is closest to g_vecAttackDir |
|
// |
|
|
|
// -Take 150% damage from club attacks. This makes crowbar duels take two hits. |
|
|
|
tdInfo.ScaleDamage( 1.50 ); |
|
|
|
#define MANHACK_PHYS_SEARCH_SIZE 64 |
|
#define MANHACK_PHYSICS_SEARCH_RADIUS 128 |
|
|
|
CBaseEntity *pList[ MANHACK_PHYS_SEARCH_SIZE ]; |
|
|
|
Vector attackDir = info.GetDamageForce(); |
|
VectorNormalize( attackDir ); |
|
|
|
Vector testCenter = GetAbsOrigin() + ( attackDir * MANHACK_PHYSICS_SEARCH_RADIUS ); |
|
Vector vecDelta( MANHACK_PHYSICS_SEARCH_RADIUS, MANHACK_PHYSICS_SEARCH_RADIUS, MANHACK_PHYSICS_SEARCH_RADIUS ); |
|
|
|
int count = UTIL_EntitiesInBox( pList, MANHACK_PHYS_SEARCH_SIZE, testCenter - vecDelta, testCenter + vecDelta, 0 ); |
|
|
|
Vector vecBestDir = g_vecAttackDir; |
|
float flBestDot = 0.90; |
|
IPhysicsObject *pPhysObj; |
|
|
|
int i; |
|
for( i = 0 ; i < count ; i++ ) |
|
{ |
|
pPhysObj = pList[ i ]->VPhysicsGetObject(); |
|
|
|
if( !pPhysObj || pPhysObj->GetMass() > 200 ) |
|
{ |
|
// Not physics. |
|
continue; |
|
} |
|
|
|
Vector center = pList[ i ]->WorldSpaceCenter(); |
|
|
|
Vector vecDirToObject; |
|
VectorSubtract( center, WorldSpaceCenter(), vecDirToObject ); |
|
VectorNormalize( vecDirToObject ); |
|
|
|
float flDot; |
|
|
|
flDot = DotProduct( g_vecAttackDir, vecDirToObject ); |
|
|
|
|
|
if( flDot > flBestDot ) |
|
{ |
|
flBestDot = flDot; |
|
vecBestDir = vecDirToObject; |
|
} |
|
} |
|
|
|
tdInfo.SetDamageForce( vecBestDir * info.GetDamage() * 200 ); |
|
|
|
// FIXME: shouldn't this happen in a base class? Anyway to prevent it from happening twice? |
|
VPhysicsTakeDamage( tdInfo ); |
|
|
|
// Force us away (no more residual speed hits!) |
|
m_vForceVelocity = vecBestDir * info.GetDamage() * 0.5f; |
|
m_flBladeSpeed = 10.0; |
|
|
|
EmitSound( "NPC_Manhack.Bat" ); |
|
|
|
// tdInfo.SetDamage( 1.0 ); |
|
|
|
m_flEngineStallTime = gpGlobals->curtime + 0.5f; |
|
StopBurst( true ); |
|
} |
|
else |
|
{ |
|
m_flBladeSpeed = 20.0; |
|
|
|
Vector vecDamageDir = tdInfo.GetDamageForce(); |
|
VectorNormalize( vecDamageDir ); |
|
|
|
m_flEngineStallTime = gpGlobals->curtime + 0.25f; |
|
m_vForceVelocity = vecDamageDir * info.GetDamage() * 20.0f; |
|
|
|
tdInfo.SetDamageForce( tdInfo.GetDamageForce() * 20 ); |
|
|
|
VPhysicsTakeDamage( info ); |
|
} |
|
|
|
int nRetVal = BaseClass::OnTakeDamage_Alive( tdInfo ); |
|
if ( nRetVal ) |
|
{ |
|
if ( m_iHealth > 0 ) |
|
{ |
|
if ( info.GetDamageType() & DMG_CLUB ) |
|
{ |
|
SetEyeState( MANHACK_EYE_STATE_STUNNED ); |
|
} |
|
|
|
if ( m_iHealth <= ( m_iMaxHealth / 2 ) ) |
|
{ |
|
CreateSmokeTrail(); |
|
} |
|
} |
|
else |
|
{ |
|
DestroySmokeTrail(); |
|
} |
|
} |
|
|
|
return nRetVal; |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose: |
|
//------------------------------------------------------------------------------ |
|
bool CNPC_Manhack::CorpseGib( const CTakeDamageInfo &info ) |
|
{ |
|
Vector vecGibVelocity; |
|
AngularImpulse vecGibAVelocity; |
|
|
|
if( info.GetDamageType() & DMG_CLUB ) |
|
{ |
|
// If clubbed to death, break apart before the attacker's eyes! |
|
vecGibVelocity = g_vecAttackDir * -150; |
|
|
|
vecGibAVelocity.x = random->RandomFloat( -2000, 2000 ); |
|
vecGibAVelocity.y = random->RandomFloat( -2000, 2000 ); |
|
vecGibAVelocity.z = random->RandomFloat( -2000, 2000 ); |
|
} |
|
else |
|
{ |
|
// Shower the pieces with my velocity. |
|
vecGibVelocity = GetCurrentVelocity(); |
|
|
|
vecGibAVelocity.x = random->RandomFloat( -500, 500 ); |
|
vecGibAVelocity.y = random->RandomFloat( -500, 500 ); |
|
vecGibAVelocity.z = random->RandomFloat( -500, 500 ); |
|
} |
|
|
|
PropBreakableCreateAll( GetModelIndex(), NULL, GetAbsOrigin(), GetAbsAngles(), vecGibVelocity, vecGibAVelocity, 1.0, 60, COLLISION_GROUP_DEBRIS ); |
|
|
|
RemoveDeferred(); |
|
|
|
KillSprites( 0.0f ); |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Explode the manhack if it's damaged while crashing |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
int CNPC_Manhack::OnTakeDamage_Dying( const CTakeDamageInfo &info ) |
|
{ |
|
// Ignore damage for the first 1 second of crashing behavior. |
|
// If we don't do this, manhacks always just explode under |
|
// sustained fire. |
|
VPhysicsTakeDamage( info ); |
|
|
|
return 0; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Turn on the engine sound if we're gagged! |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::OnStateChange( NPC_STATE OldState, NPC_STATE NewState ) |
|
{ |
|
if( m_vNoiseMod.z == MANHACK_NOISEMOD_HIDE && !(m_spawnflags & SF_NPC_WAIT_FOR_SCRIPT) && !(m_spawnflags & SF_MANHACK_PACKED_UP) ) |
|
{ |
|
// This manhack should get a normal noisemod now. |
|
float flNoiseMod = random->RandomFloat( 1.7, 2.3 ); |
|
|
|
// Just bob up and down. |
|
SetNoiseMod( 0, 0, flNoiseMod ); |
|
} |
|
|
|
if( NewState != NPC_STATE_IDLE && (m_spawnflags & SF_NPC_GAG) && (m_nEnginePitch1 < 0) ) |
|
{ |
|
m_spawnflags &= ~SF_NPC_GAG; |
|
SoundInit(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : Type - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::HandleAnimEvent( animevent_t *pEvent ) |
|
{ |
|
Vector vecNewVelocity; |
|
switch( pEvent->event ) |
|
{ |
|
case MANHACK_AE_START_ENGINE: |
|
StartEye(); |
|
StartEngine( true ); |
|
m_spawnflags &= ~SF_MANHACK_PACKED_UP; |
|
|
|
// No bursts until fully unpacked! |
|
m_flNextBurstTime = gpGlobals->curtime + FLT_MAX; |
|
break; |
|
|
|
case MANHACK_AE_DONE_UNPACKING: |
|
m_flNextBurstTime = gpGlobals->curtime + 2.0; |
|
break; |
|
|
|
case MANHACK_AE_OPEN_BLADE: |
|
m_bBladesActive = true; |
|
break; |
|
|
|
default: |
|
BaseClass::HandleAnimEvent( pEvent ); |
|
break; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Returns whether or not the given activity would translate to flying. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_Manhack::IsFlyingActivity( Activity baseAct ) |
|
{ |
|
return ((baseAct == ACT_FLY || baseAct == ACT_IDLE || baseAct == ACT_RUN || baseAct == ACT_WALK) && m_bBladesActive); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : Type - |
|
//----------------------------------------------------------------------------- |
|
Activity CNPC_Manhack::NPC_TranslateActivity( Activity baseAct ) |
|
{ |
|
if (IsFlyingActivity( baseAct )) |
|
{ |
|
return (Activity)ACT_FLY; |
|
} |
|
|
|
return BaseClass::NPC_TranslateActivity( baseAct ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : Type - |
|
//----------------------------------------------------------------------------- |
|
int CNPC_Manhack::TranslateSchedule( int scheduleType ) |
|
{ |
|
// Fail-safe for deployment if packed up and interrupted |
|
if ( m_spawnflags & SF_MANHACK_PACKED_UP ) |
|
{ |
|
if ( scheduleType != SCHED_WAIT_FOR_SCRIPT ) |
|
return SCHED_MANHACK_DEPLOY; |
|
} |
|
|
|
switch ( scheduleType ) |
|
{ |
|
case SCHED_MELEE_ATTACK1: |
|
{ |
|
return SCHED_MANHACK_ATTACK_HOVER; |
|
break; |
|
} |
|
case SCHED_BACK_AWAY_FROM_ENEMY: |
|
{ |
|
return SCHED_MANHACK_REGROUP; |
|
break; |
|
} |
|
case SCHED_CHASE_ENEMY: |
|
{ |
|
// If we're waiting for our next attack opportunity, just swarm |
|
if ( m_flNextBurstTime > gpGlobals->curtime ) |
|
{ |
|
return SCHED_MANHACK_SWARM; |
|
} |
|
|
|
if ( !m_bDoSwarmBehavior || OccupyStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 ) ) |
|
{ |
|
return SCHED_CHASE_ENEMY; |
|
} |
|
else |
|
{ |
|
return SCHED_MANHACK_SWARM; |
|
} |
|
} |
|
case SCHED_COMBAT_FACE: |
|
{ |
|
// Don't care about facing enemy, handled automatically |
|
return TranslateSchedule( SCHED_CHASE_ENEMY ); |
|
break; |
|
} |
|
case SCHED_WAKE_ANGRY: |
|
{ |
|
if( m_spawnflags & SF_MANHACK_PACKED_UP ) |
|
{ |
|
return SCHED_MANHACK_DEPLOY; |
|
} |
|
else |
|
{ |
|
return TranslateSchedule( SCHED_CHASE_ENEMY ); |
|
} |
|
break; |
|
} |
|
|
|
case SCHED_IDLE_STAND: |
|
case SCHED_ALERT_STAND: |
|
case SCHED_ALERT_FACE: |
|
{ |
|
if ( m_pSquad && m_bDoSwarmBehavior ) |
|
{ |
|
return SCHED_MANHACK_SWARM_IDLE; |
|
} |
|
else |
|
{ |
|
return BaseClass::TranslateSchedule(scheduleType); |
|
} |
|
} |
|
|
|
case SCHED_CHASE_ENEMY_FAILED: |
|
{ |
|
// Relentless bastard! Doesn't fail (fail not valid anyway) |
|
return TranslateSchedule( SCHED_CHASE_ENEMY ); |
|
break; |
|
} |
|
|
|
} |
|
return BaseClass::TranslateSchedule(scheduleType); |
|
} |
|
|
|
#define MAX_LOITER_DIST_SQR 144 // (12 inches sqr) |
|
void CNPC_Manhack::Loiter() |
|
{ |
|
//NDebugOverlay::Line( GetAbsOrigin(), m_vecLoiterPosition, 255, 255, 255, false, 0.1 ); |
|
|
|
// Friendly manhack is loitering. |
|
if( !m_bHeld ) |
|
{ |
|
float distSqr = m_vecLoiterPosition.DistToSqr(GetAbsOrigin()); |
|
|
|
if( distSqr > MAX_LOITER_DIST_SQR ) |
|
{ |
|
Vector vecDir = m_vecLoiterPosition - GetAbsOrigin(); |
|
VectorNormalize( vecDir ); |
|
|
|
// Move back to our loiter position. |
|
if( gpGlobals->curtime > m_fTimeNextLoiterPulse ) |
|
{ |
|
// Apply a pulse of force if allowed right now. |
|
if( distSqr > MAX_LOITER_DIST_SQR * 4.0f ) |
|
{ |
|
//Msg("Big Pulse\n"); |
|
m_vForceVelocity = vecDir * 12.0f; |
|
} |
|
else |
|
{ |
|
//Msg("Small Pulse\n"); |
|
m_vForceVelocity = vecDir * 6.0f; |
|
} |
|
|
|
m_fTimeNextLoiterPulse = gpGlobals->curtime + 1.0f; |
|
} |
|
else |
|
{ |
|
m_vForceVelocity = vec3_origin; |
|
} |
|
} |
|
else |
|
{ |
|
// Counteract velocity to slow down. |
|
Vector velocity = GetCurrentVelocity(); |
|
m_vForceVelocity = velocity * -0.5; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::MaintainGroundHeight( void ) |
|
{ |
|
float zSpeed = GetCurrentVelocity().z; |
|
|
|
if ( zSpeed > 32.0f ) |
|
return; |
|
|
|
const float minGroundHeight = 52.0f; |
|
|
|
trace_t tr; |
|
AI_TraceHull( GetAbsOrigin(), |
|
GetAbsOrigin() - Vector( 0, 0, minGroundHeight ), |
|
GetHullMins(), |
|
GetHullMaxs(), |
|
(MASK_NPCSOLID_BRUSHONLY), |
|
this, |
|
COLLISION_GROUP_NONE, |
|
&tr ); |
|
|
|
if ( tr.fraction != 1.0f ) |
|
{ |
|
float speedAdj = MAX( 16, (-zSpeed*0.5f) ); |
|
|
|
m_vForceVelocity += Vector(0,0,1) * ( speedAdj * ( 1.0f - tr.fraction ) ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles movement towards the last move target. |
|
// Input : flInterval - |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_Manhack::OverrideMove( float flInterval ) |
|
{ |
|
SpinBlades( flInterval ); |
|
|
|
// Don't execute any move code if packed up. |
|
if( HasSpawnFlags(SF_MANHACK_PACKED_UP|SF_MANHACK_CARRIED) ) |
|
return true; |
|
|
|
if( IsLoitering() ) |
|
{ |
|
Loiter(); |
|
} |
|
else |
|
{ |
|
MaintainGroundHeight(); |
|
} |
|
|
|
// So cops, etc. will try to avoid them |
|
if ( !HasSpawnFlags( SF_MANHACK_NO_DANGER_SOUNDS ) && !m_bHeld ) |
|
{ |
|
CSoundEnt::InsertSound( SOUND_DANGER, GetAbsOrigin(), 75, flInterval, this ); |
|
} |
|
|
|
// ----------------------------------------------------------------- |
|
// If I'm being forced to move somewhere |
|
// ------------------------------------------------------------------ |
|
if (m_fForceMoveTime > gpGlobals->curtime) |
|
{ |
|
MoveToTarget(flInterval, m_vForceMoveTarget); |
|
} |
|
// ----------------------------------------------------------------- |
|
// If I have a route, keep it updated and move toward target |
|
// ------------------------------------------------------------------ |
|
else if (GetNavigator()->IsGoalActive()) |
|
{ |
|
bool bReducible = GetNavigator()->GetPath()->GetCurWaypoint()->IsReducible(); |
|
const float strictTolerance = 64.0; |
|
//NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + Vector(0, 0, 10 ), 255, 0, 0, true, 0.1); |
|
if ( ProgressFlyPath( flInterval, GetEnemy(), MoveCollisionMask(), bReducible, strictTolerance ) == AINPP_COMPLETE ) |
|
return true; |
|
} |
|
// ----------------------------------------------------------------- |
|
// If I'm supposed to swarm somewhere, try to go there |
|
// ------------------------------------------------------------------ |
|
else if (m_fSwarmMoveTime > gpGlobals->curtime) |
|
{ |
|
MoveToTarget(flInterval, m_vSwarmMoveTarget); |
|
} |
|
// ----------------------------------------------------------------- |
|
// If I don't have anything better to do, just decelerate |
|
// -------------------------------------------------------------- ---- |
|
else |
|
{ |
|
float myDecay = 9.5; |
|
Decelerate( flInterval, myDecay ); |
|
|
|
m_vTargetBanking = vec3_origin; |
|
|
|
// ------------------------------------- |
|
// If I have an enemy turn to face him |
|
// ------------------------------------- |
|
if (GetEnemy()) |
|
{ |
|
TurnHeadToTarget(flInterval, GetEnemy()->EyePosition() ); |
|
} |
|
} |
|
|
|
if ( m_iHealth <= 0 ) |
|
{ |
|
// Crashing!! |
|
MoveExecute_Dead(flInterval); |
|
} |
|
else |
|
{ |
|
// Alive! |
|
MoveExecute_Alive(flInterval); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::TurnHeadRandomly(float flInterval ) |
|
{ |
|
float desYaw = random->RandomFloat(0,360); |
|
|
|
float iRate = 0.8; |
|
// Make frame rate independent |
|
float timeToUse = flInterval; |
|
while (timeToUse > 0) |
|
{ |
|
m_fHeadYaw = (iRate * m_fHeadYaw) + (1-iRate)*desYaw; |
|
timeToUse = -0.1; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::MoveToTarget(float flInterval, const Vector &vMoveTarget) |
|
{ |
|
if (flInterval <= 0) |
|
{ |
|
return; |
|
} |
|
|
|
// ----------------------------------------- |
|
// Don't steer if engine's have stalled |
|
// ----------------------------------------- |
|
if ( gpGlobals->curtime < m_flEngineStallTime || m_iHealth <= 0 ) |
|
return; |
|
|
|
if ( GetEnemy() != NULL ) |
|
{ |
|
TurnHeadToTarget( flInterval, GetEnemy()->EyePosition() ); |
|
} |
|
else |
|
{ |
|
TurnHeadToTarget( flInterval, vMoveTarget ); |
|
} |
|
|
|
// ------------------------------------- |
|
// Move towards our target |
|
// ------------------------------------- |
|
float myAccel; |
|
float myZAccel = 300.0f; |
|
float myDecay = 0.3f; |
|
|
|
Vector targetDir; |
|
float flDist; |
|
|
|
// If we're bursting, just head straight |
|
if ( m_flBurstDuration > gpGlobals->curtime ) |
|
{ |
|
float zDist = 500; |
|
|
|
// Steer towards our enemy if we're able to |
|
if ( GetEnemy() != NULL ) |
|
{ |
|
Vector steerDir = ( GetEnemy()->EyePosition() - GetAbsOrigin() ); |
|
zDist = fabs( steerDir.z ); |
|
VectorNormalize( steerDir ); |
|
|
|
float useTime = flInterval; |
|
while ( useTime > 0.0f ) |
|
{ |
|
m_vecBurstDirection += ( steerDir * 4.0f ); |
|
useTime -= 0.1f; |
|
} |
|
|
|
m_vecBurstDirection.z = steerDir.z; |
|
|
|
VectorNormalize( m_vecBurstDirection ); |
|
} |
|
|
|
// Debug visualizations |
|
/* |
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( targetDir * 64.0f ), 255, 0, 0, true, 2.1f ); |
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( steerDir * 64.0f ), 0, 255, 0, true, 2.1f ); |
|
NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + ( m_vecBurstDirection * 64.0f ), 0, 0, 255, true, 2.1f ); |
|
NDebugOverlay::Cross3D( GetAbsOrigin() , -Vector(8,8,8), Vector(8,8,8), 255, 0, 0, true, 2.1f ); |
|
*/ |
|
|
|
targetDir = m_vecBurstDirection; |
|
|
|
flDist = FLT_MAX; |
|
myDecay = 0.3f; |
|
#ifdef _XBOX |
|
myAccel = 500; |
|
#else |
|
myAccel = 400; |
|
#endif // _XBOX |
|
myZAccel = MIN( 500, zDist / flInterval ); |
|
} |
|
else |
|
{ |
|
Vector vecCurrentDir = GetCurrentVelocity(); |
|
VectorNormalize( vecCurrentDir ); |
|
|
|
targetDir = vMoveTarget - GetAbsOrigin(); |
|
flDist = VectorNormalize( targetDir ); |
|
|
|
float flDot = DotProduct( targetDir, vecCurrentDir ); |
|
|
|
// Otherwise we should steer towards our goal |
|
if( flDot > 0.25 ) |
|
{ |
|
// If my target is in front of me, my flight model is a bit more accurate. |
|
myAccel = 300; |
|
} |
|
else |
|
{ |
|
// Have a harder time correcting my course if I'm currently flying away from my target. |
|
myAccel = 200; |
|
} |
|
} |
|
|
|
// Clamp lateral acceleration |
|
if ( myAccel > ( flDist / flInterval ) ) |
|
{ |
|
myAccel = flDist / flInterval; |
|
} |
|
|
|
/* |
|
// Boost vertical movement |
|
if ( targetDir.z > 0 ) |
|
{ |
|
// Z acceleration is faster when we thrust upwards. |
|
// This is to help keep manhacks out of water. |
|
myZAccel *= 5.0; |
|
} |
|
*/ |
|
|
|
// Clamp vertical movement |
|
if ( myZAccel > flDist / flInterval ) |
|
{ |
|
myZAccel = flDist / flInterval; |
|
} |
|
|
|
// Scale by our engine force |
|
myAccel *= m_fEnginePowerScale; |
|
myZAccel *= m_fEnginePowerScale; |
|
|
|
MoveInDirection( flInterval, targetDir, myAccel, myZAccel, myDecay ); |
|
|
|
// calc relative banking targets |
|
Vector forward, right; |
|
GetVectors( &forward, &right, NULL ); |
|
m_vTargetBanking.x = 40 * DotProduct( forward, targetDir ); |
|
m_vTargetBanking.z = 40 * DotProduct( right, targetDir ); |
|
m_vTargetBanking.y = 0.0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Ignore water if I'm close to my enemy |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
int CNPC_Manhack::MoveCollisionMask(void) |
|
{ |
|
return MASK_NPCSOLID; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Make a splash effect |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::Splash( const Vector &vecSplashPos ) |
|
{ |
|
CEffectData data; |
|
|
|
data.m_fFlags = 0; |
|
data.m_vOrigin = vecSplashPos; |
|
data.m_vNormal = Vector( 0, 0, 1 ); |
|
|
|
data.m_flScale = 8.0f; |
|
|
|
int contents = GetWaterType(); |
|
|
|
// Verify we have valid contents |
|
if ( !( contents & (CONTENTS_SLIME|CONTENTS_WATER))) |
|
{ |
|
// We're leaving the water so we have to reverify what it was |
|
trace_t tr; |
|
UTIL_TraceLine( GetAbsOrigin(), GetAbsOrigin() - Vector( 0, 0, 256 ), (CONTENTS_WATER|CONTENTS_SLIME), this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
// Re-validate this |
|
if ( !(tr.contents&(CONTENTS_WATER|CONTENTS_SLIME)) ) |
|
{ |
|
//NOTENOTE: We called a splash but we don't seem to be near water? |
|
Assert( 0 ); |
|
return; |
|
} |
|
|
|
contents = tr.contents; |
|
} |
|
|
|
// Mark us if we're in slime |
|
if ( contents & CONTENTS_SLIME ) |
|
{ |
|
data.m_fFlags |= FX_WATER_IN_SLIME; |
|
} |
|
|
|
DispatchEffect( "watersplash", data ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes the slice bounce velocity |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::ComputeSliceBounceVelocity( CBaseEntity *pHitEntity, trace_t &tr ) |
|
{ |
|
if( pHitEntity->IsAlive() && FClassnameIs( pHitEntity, "func_breakable_surf" ) ) |
|
{ |
|
// We want to see if the manhack hits a breakable pane of glass. To keep from checking |
|
// The classname of the HitEntity on each impact, we only do this check if we hit |
|
// something that's alive. Anyway, prevent the manhack bouncing off the pane of glass, |
|
// since this impact will shatter the glass and let the manhack through. |
|
return; |
|
} |
|
|
|
Vector vecDir; |
|
|
|
// If the manhack isn't bouncing away from whatever he sliced, force it. |
|
VectorSubtract( WorldSpaceCenter(), pHitEntity->WorldSpaceCenter(), vecDir ); |
|
VectorNormalize( vecDir ); |
|
vecDir *= 200; |
|
vecDir[2] = 0.0f; |
|
|
|
// Knock it away from us |
|
if ( VPhysicsGetObject() != NULL ) |
|
{ |
|
VPhysicsGetObject()->ApplyForceOffset( vecDir * 4, GetAbsOrigin() ); |
|
} |
|
|
|
// Also set our velocity |
|
SetCurrentVelocity( vecDir ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Is the manhack being held? |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_Manhack::IsHeldByPhyscannon( ) |
|
{ |
|
return VPhysicsGetObject() && (VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_PLAYER_HELD); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: We've touched something that we can hurt. Slice it! |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::Slice( CBaseEntity *pHitEntity, float flInterval, trace_t &tr ) |
|
{ |
|
// Don't hurt the player if I'm in water |
|
if( GetWaterLevel() > 0 && pHitEntity->IsPlayer() ) |
|
return; |
|
|
|
// Can't slice players holding it with the phys cannon |
|
if ( IsHeldByPhyscannon() ) |
|
{ |
|
if ( pHitEntity && (pHitEntity == HasPhysicsAttacker( FLT_MAX )) ) |
|
return; |
|
} |
|
|
|
if ( pHitEntity->m_takedamage == DAMAGE_NO ) |
|
return; |
|
|
|
// Damage must be scaled by flInterval so framerate independent |
|
float flDamage = sk_manhack_melee_dmg.GetFloat() * flInterval; |
|
|
|
if ( pHitEntity->IsPlayer() ) |
|
{ |
|
flDamage *= 2.0f; |
|
} |
|
|
|
// Held manhacks do more damage |
|
if ( IsHeldByPhyscannon() ) |
|
{ |
|
// Deal 100 damage/sec |
|
flDamage = 100.0f * flInterval; |
|
} |
|
else if ( pHitEntity->IsNPC() && HasPhysicsAttacker( MANHACK_SMASH_TIME ) ) |
|
{ |
|
extern ConVar sk_combine_guard_health; |
|
// NOTE: The else here is essential. |
|
// The physics attacker *will* be set even when the manhack is held |
|
flDamage = sk_combine_guard_health.GetFloat(); // the highest healthed fleshy enemy |
|
} |
|
else if ( dynamic_cast<CBaseProp*>(pHitEntity) || dynamic_cast<CBreakable*>(pHitEntity) ) |
|
{ |
|
// If we hit a prop, we want it to break immediately |
|
flDamage = pHitEntity->GetHealth(); |
|
} |
|
else if ( pHitEntity->IsNPC() && IRelationType( pHitEntity ) == D_HT && FClassnameIs( pHitEntity, "npc_combine_s" ) ) |
|
{ |
|
flDamage *= 6.0f; |
|
} |
|
|
|
if (flDamage < 1.0f) |
|
{ |
|
flDamage = 1.0f; |
|
} |
|
|
|
CTakeDamageInfo info( this, this, flDamage, DMG_SLASH ); |
|
|
|
// check for actual "ownership" of damage |
|
CBasePlayer *pPlayer = HasPhysicsAttacker( MANHACK_SMASH_TIME ); |
|
if (pPlayer) |
|
{ |
|
info.SetAttacker( pPlayer ); |
|
} |
|
|
|
Vector dir = (tr.endpos - tr.startpos); |
|
if ( dir == vec3_origin ) |
|
{ |
|
dir = tr.m_pEnt->GetAbsOrigin() - GetAbsOrigin(); |
|
} |
|
CalculateMeleeDamageForce( &info, dir, tr.endpos ); |
|
pHitEntity->TakeDamage( info ); |
|
|
|
// Spawn some extra blood where we hit |
|
if ( pHitEntity->BloodColor() == DONT_BLEED ) |
|
{ |
|
CEffectData data; |
|
Vector velocity = GetCurrentVelocity(); |
|
|
|
data.m_vOrigin = tr.endpos; |
|
data.m_vAngles = GetAbsAngles(); |
|
|
|
VectorNormalize( velocity ); |
|
|
|
data.m_vNormal = ( tr.plane.normal + velocity ) * 0.5;; |
|
|
|
DispatchEffect( "ManhackSparks", data ); |
|
|
|
EmitSound( "NPC_Manhack.Grind" ); |
|
|
|
//TODO: What we really want to do is get a material reference and emit the proper sprayage! - jdw |
|
} |
|
else |
|
{ |
|
SpawnBlood(tr.endpos, g_vecAttackDir, pHitEntity->BloodColor(), 6 ); |
|
EmitSound( "NPC_Manhack.Slice" ); |
|
} |
|
|
|
// Pop back a little bit after hitting the player |
|
ComputeSliceBounceVelocity( pHitEntity, tr ); |
|
|
|
// Save off when we last hit something |
|
m_flLastDamageTime = gpGlobals->curtime; |
|
|
|
// Reset our state and give the player time to react |
|
StopBurst( true ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: We've touched something solid. Just bump it. |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::Bump( CBaseEntity *pHitEntity, float flInterval, trace_t &tr ) |
|
{ |
|
if ( !VPhysicsGetObject() ) |
|
return; |
|
|
|
// Surpressing this behavior |
|
if ( m_flBumpSuppressTime > gpGlobals->curtime ) |
|
return; |
|
|
|
if ( pHitEntity->GetMoveType() == MOVETYPE_VPHYSICS && pHitEntity->Classify()!=CLASS_MANHACK ) |
|
{ |
|
HitPhysicsObject( pHitEntity ); |
|
} |
|
|
|
// We've hit something so deflect our velocity based on the surface |
|
// norm of what we've hit |
|
if (flInterval > 0) |
|
{ |
|
float moveLen = ( (GetCurrentVelocity() * flInterval) * (1.0 - tr.fraction) ).Length(); |
|
|
|
Vector moveVec = moveLen*tr.plane.normal/flInterval; |
|
|
|
// If I'm totally dead, don't bounce me up |
|
if (m_iHealth <=0 && moveVec.z > 0) |
|
{ |
|
moveVec.z = 0; |
|
} |
|
|
|
// If I'm right over the ground don't push down |
|
if (moveVec.z < 0) |
|
{ |
|
float floorZ = GetFloorZ(GetAbsOrigin()); |
|
if (abs(GetAbsOrigin().z - floorZ) < 36) |
|
{ |
|
moveVec.z = 0; |
|
} |
|
} |
|
|
|
Vector myUp; |
|
VPhysicsGetObject()->LocalToWorldVector( &myUp, Vector( 0.0, 0.0, 1.0 ) ); |
|
|
|
// plane must be something that could hit the blades |
|
if (fabs( DotProduct( myUp, tr.plane.normal ) ) < 0.25 ) |
|
{ |
|
CEffectData data; |
|
Vector velocity = GetCurrentVelocity(); |
|
|
|
data.m_vOrigin = tr.endpos; |
|
data.m_vAngles = GetAbsAngles(); |
|
|
|
VectorNormalize( velocity ); |
|
|
|
data.m_vNormal = ( tr.plane.normal + velocity ) * 0.5;; |
|
|
|
DispatchEffect( "ManhackSparks", data ); |
|
|
|
CBroadcastRecipientFilter filter; |
|
|
|
te->DynamicLight( filter, 0.0, &GetAbsOrigin(), 255, 180, 100, 0, 50, 0.3, 150 ); |
|
|
|
// add some spin, but only if we're not already going fast.. |
|
Vector vecVelocity; |
|
AngularImpulse vecAngVelocity; |
|
VPhysicsGetObject()->GetVelocity( &vecVelocity, &vecAngVelocity ); |
|
float flDot = DotProduct( myUp, vecAngVelocity ); |
|
if ( fabs(flDot) < 100 ) |
|
{ |
|
//AngularImpulse torque = myUp * (1000 - flDot * 10); |
|
AngularImpulse torque = myUp * (1000 - flDot * 2); |
|
VPhysicsGetObject()->ApplyTorqueCenter( torque ); |
|
} |
|
|
|
if (!(m_spawnflags & SF_NPC_GAG)) |
|
{ |
|
EmitSound( "NPC_Manhack.Grind" ); |
|
} |
|
|
|
// For decals and sparks we must trace a line in the direction of the surface norm |
|
// that we hit. |
|
trace_t decalTrace; |
|
AI_TraceLine( GetAbsOrigin(), GetAbsOrigin() - (tr.plane.normal * 24),MASK_SOLID, this, COLLISION_GROUP_NONE, &decalTrace ); |
|
|
|
if ( decalTrace.fraction != 1.0 ) |
|
{ |
|
// Leave decal only if colliding horizontally |
|
if ((DotProduct(Vector(0,0,1),decalTrace.plane.normal)<0.5) && (DotProduct(Vector(0,0,-1),decalTrace.plane.normal)<0.5)) |
|
{ |
|
UTIL_DecalTrace( &decalTrace, "ManhackCut" ); |
|
} |
|
} |
|
} |
|
|
|
// See if we will not have a valid surface |
|
if ( tr.allsolid || tr.startsolid ) |
|
{ |
|
// Build a fake reflection back along our current velocity because we can't know how to reflect off |
|
// a non-existant surface! -- jdw |
|
|
|
Vector vecRandomDir = RandomVector( -1.0f, 1.0f ); |
|
SetCurrentVelocity( vecRandomDir * 50.0f ); |
|
m_flBumpSuppressTime = gpGlobals->curtime + 0.5f; |
|
} |
|
else |
|
{ |
|
// This is a valid hit and we can deflect properly |
|
|
|
VectorNormalize( moveVec ); |
|
float hitAngle = -DotProduct( tr.plane.normal, -moveVec ); |
|
|
|
Vector vReflection = 2.0 * tr.plane.normal * hitAngle + -moveVec; |
|
|
|
float flSpeed = GetCurrentVelocity().Length(); |
|
SetCurrentVelocity( GetCurrentVelocity() + vReflection * flSpeed * 0.5f ); |
|
} |
|
} |
|
|
|
// ------------------------------------------------------------- |
|
// If I'm on a path check LOS to my next node, and fail on path |
|
// if I don't have LOS. Note this is the only place I do this, |
|
// so the manhack has to collide before failing on a path |
|
// ------------------------------------------------------------- |
|
if (GetNavigator()->IsGoalActive() && !(GetNavigator()->GetPath()->CurWaypointFlags() & bits_WP_TO_PATHCORNER) ) |
|
{ |
|
AIMoveTrace_t moveTrace; |
|
GetMoveProbe()->MoveLimit( NAV_GROUND, GetAbsOrigin(), GetNavigator()->GetCurWaypointPos(), |
|
MoveCollisionMask(), GetEnemy(), &moveTrace ); |
|
|
|
if (IsMoveBlocked( moveTrace ) && |
|
!moveTrace.pObstruction->ClassMatches( GetClassname() )) |
|
{ |
|
TaskFail(FAIL_NO_ROUTE); |
|
GetNavigator()->ClearGoal(); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::CheckCollisions(float flInterval) |
|
{ |
|
// Trace forward to see if I hit anything. But trace forward along the |
|
// owner's view direction if you're being carried. |
|
Vector vecTraceDir, vecCheckPos; |
|
VPhysicsGetObject()->GetVelocity( &vecTraceDir, NULL ); |
|
vecTraceDir *= flInterval; |
|
if ( IsHeldByPhyscannon() ) |
|
{ |
|
CBasePlayer *pCarrier = HasPhysicsAttacker( FLT_MAX ); |
|
if ( pCarrier ) |
|
{ |
|
if ( pCarrier->CollisionProp()->CalcDistanceFromPoint( WorldSpaceCenter() ) < 30 ) |
|
{ |
|
AngleVectors( pCarrier->EyeAngles(), &vecTraceDir, NULL, NULL ); |
|
vecTraceDir *= 40.0f; |
|
} |
|
} |
|
} |
|
|
|
VectorAdd( GetAbsOrigin(), vecTraceDir, vecCheckPos ); |
|
|
|
trace_t tr; |
|
CBaseEntity* pHitEntity = NULL; |
|
|
|
AI_TraceHull( GetAbsOrigin(), |
|
vecCheckPos, |
|
GetHullMins(), |
|
GetHullMaxs(), |
|
MoveCollisionMask(), |
|
this, |
|
COLLISION_GROUP_NONE, |
|
&tr ); |
|
|
|
if ( (tr.fraction != 1.0 || tr.startsolid) && tr.m_pEnt) |
|
{ |
|
PhysicsMarkEntitiesAsTouching( tr.m_pEnt, tr ); |
|
pHitEntity = tr.m_pEnt; |
|
|
|
if( m_bHeld && tr.m_pEnt->MyNPCPointer() && tr.m_pEnt->MyNPCPointer()->IsPlayerAlly() ) |
|
{ |
|
// Don't slice Alyx when she approaches to hack. We need a better solution for this!! |
|
//Msg("Ignoring!\n"); |
|
return; |
|
} |
|
|
|
if ( pHitEntity != NULL && |
|
pHitEntity->m_takedamage == DAMAGE_YES && |
|
pHitEntity->Classify() != CLASS_MANHACK && |
|
gpGlobals->curtime > m_flWaterSuspendTime ) |
|
{ |
|
// Slice this thing |
|
Slice( pHitEntity, flInterval, tr ); |
|
m_flBladeSpeed = 20.0; |
|
} |
|
else |
|
{ |
|
// Just bump into this thing. |
|
Bump( pHitEntity, flInterval, tr ); |
|
m_flBladeSpeed = 20.0; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
#define tempTIME_STEP = 0.5; |
|
void CNPC_Manhack::PlayFlySound(void) |
|
{ |
|
float flEnemyDist; |
|
|
|
if( GetEnemy() ) |
|
{ |
|
flEnemyDist = (GetAbsOrigin() - GetEnemy()->GetAbsOrigin()).Length(); |
|
} |
|
else |
|
{ |
|
flEnemyDist = FLT_MAX; |
|
} |
|
|
|
if( m_spawnflags & SF_NPC_GAG ) |
|
{ |
|
// Quiet! |
|
return; |
|
} |
|
|
|
if( m_flWaterSuspendTime > gpGlobals->curtime ) |
|
{ |
|
// Just went in water. Slow the motor!! |
|
if( m_bDirtyPitch ) |
|
{ |
|
m_nEnginePitch1 = MANHACK_WATER_PITCH1; |
|
m_flEnginePitch1Time = gpGlobals->curtime + 0.5f; |
|
m_nEnginePitch2 = MANHACK_WATER_PITCH2; |
|
m_flEnginePitch2Time = gpGlobals->curtime + 0.5f; |
|
m_bDirtyPitch = false; |
|
} |
|
} |
|
// Spin sound based on distance from enemy (unless we're crashing) |
|
else if (GetEnemy() && IsAlive() ) |
|
{ |
|
if( flEnemyDist < MANHACK_PITCH_DIST1 ) |
|
{ |
|
// recalculate pitch. |
|
int iPitch1, iPitch2; |
|
float flDistFactor; |
|
|
|
flDistFactor = MIN( 1.0, 1 - flEnemyDist / MANHACK_PITCH_DIST1 ); |
|
iPitch1 = MANHACK_MIN_PITCH1 + ( ( MANHACK_MAX_PITCH1 - MANHACK_MIN_PITCH1 ) * flDistFactor); |
|
|
|
// NOTE: MANHACK_PITCH_DIST2 must be < MANHACK_PITCH_DIST1 |
|
flDistFactor = MIN( 1.0, 1 - flEnemyDist / MANHACK_PITCH_DIST2 ); |
|
iPitch2 = MANHACK_MIN_PITCH2 + ( ( MANHACK_MAX_PITCH2 - MANHACK_MIN_PITCH2 ) * flDistFactor); |
|
|
|
m_nEnginePitch1 = iPitch1; |
|
m_flEnginePitch1Time = gpGlobals->curtime + 0.1f; |
|
m_nEnginePitch2 = iPitch2; |
|
m_flEnginePitch2Time = gpGlobals->curtime + 0.1f; |
|
|
|
m_bDirtyPitch = true; |
|
} |
|
else if( m_bDirtyPitch ) |
|
{ |
|
m_nEnginePitch1 = MANHACK_MIN_PITCH1; |
|
m_flEnginePitch1Time = gpGlobals->curtime + 0.1f; |
|
m_nEnginePitch2 = MANHACK_MIN_PITCH2; |
|
m_flEnginePitch2Time = gpGlobals->curtime + 0.2f; |
|
m_bDirtyPitch = false; |
|
} |
|
} |
|
// If no enemy just play low sound |
|
else if( IsAlive() && m_bDirtyPitch ) |
|
{ |
|
m_nEnginePitch1 = MANHACK_MIN_PITCH1; |
|
m_flEnginePitch1Time = gpGlobals->curtime + 0.1f; |
|
m_nEnginePitch2 = MANHACK_MIN_PITCH2; |
|
m_flEnginePitch2Time = gpGlobals->curtime + 0.2f; |
|
|
|
m_bDirtyPitch = false; |
|
} |
|
|
|
// Play special engine every once in a while |
|
if (gpGlobals->curtime > m_flNextEngineSoundTime && flEnemyDist < 48) |
|
{ |
|
m_flNextEngineSoundTime = gpGlobals->curtime + random->RandomFloat( 3.0, 10.0 ); |
|
|
|
EmitSound( "NPC_Manhack.EngineNoise" ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::MoveExecute_Alive(float flInterval) |
|
{ |
|
PhysicsCheckWaterTransition(); |
|
|
|
Vector vCurrentVelocity = GetCurrentVelocity(); |
|
|
|
// FIXME: move this |
|
VPhysicsGetObject()->Wake(); |
|
|
|
if( m_fEnginePowerScale < GetMaxEnginePower() && gpGlobals->curtime > m_flWaterSuspendTime ) |
|
{ |
|
// Power is low, and we're no longer stuck in water, so bring power up. |
|
m_fEnginePowerScale += 0.05; |
|
} |
|
|
|
// ---------------------------------------------------------------------------------------- |
|
// Add time-coherent noise to the current velocity so that it never looks bolted in place. |
|
// ---------------------------------------------------------------------------------------- |
|
float noiseScale = 7.0f; |
|
|
|
if ( (CBaseEntity*)GetEnemy() ) |
|
{ |
|
float flDist = (GetAbsOrigin() - GetEnemy()->GetAbsOrigin()).Length2D(); |
|
|
|
if( flDist < MANHACK_CHARGE_MIN_DIST ) |
|
{ |
|
// Less noise up close. |
|
noiseScale = 2.0; |
|
} |
|
|
|
if ( IsInEffectiveTargetZone( GetEnemy() ) && flDist < MANHACK_CHARGE_MIN_DIST && gpGlobals->curtime > m_flNextBurstTime ) |
|
{ |
|
Vector vecCurrentDir = GetCurrentVelocity(); |
|
VectorNormalize( vecCurrentDir ); |
|
|
|
Vector vecToEnemy = ( GetEnemy()->EyePosition() - WorldSpaceCenter() ); |
|
VectorNormalize( vecToEnemy ); |
|
|
|
float flDot = DotProduct( vecCurrentDir, vecToEnemy ); |
|
|
|
if ( flDot > 0.75 ) |
|
{ |
|
Vector offsetDir = ( vecToEnemy - vecCurrentDir ); |
|
VectorNormalize( offsetDir ); |
|
|
|
Vector offsetSpeed = GetCurrentVelocity() * flDot; |
|
|
|
//FIXME: This code sucks -- jdw |
|
|
|
offsetDir.z = 0.0f; |
|
m_vForceVelocity += ( offsetDir * ( offsetSpeed.Length2D() * 0.25f ) ); |
|
|
|
// Commit to the attack- no steering for about a second |
|
StartBurst( vecToEnemy ); |
|
SetEyeState( MANHACK_EYE_STATE_CHARGE ); |
|
} |
|
} |
|
|
|
if ( gpGlobals->curtime > m_flBurstDuration ) |
|
{ |
|
ShowHostile( false ); |
|
} |
|
} |
|
|
|
// ---------------------------------------------------------------------------------------- |
|
// Add in any forced velocity |
|
// ---------------------------------------------------------------------------------------- |
|
SetCurrentVelocity( vCurrentVelocity + m_vForceVelocity ); |
|
m_vForceVelocity = vec3_origin; |
|
|
|
if( !m_bHackedByAlyx || GetEnemy() ) |
|
{ |
|
// If hacked and no enemy, don't drift! |
|
AddNoiseToVelocity( noiseScale ); |
|
} |
|
|
|
LimitSpeed( 200, ManhackMaxSpeed() ); |
|
|
|
if( m_flWaterSuspendTime > gpGlobals->curtime ) |
|
{ |
|
if( UTIL_PointContents( GetAbsOrigin() ) & (CONTENTS_WATER|CONTENTS_SLIME) ) |
|
{ |
|
// Ooops, we're submerged somehow. Move upwards until our origin is out of the water. |
|
m_vCurrentVelocity.z = 20.0; |
|
} |
|
else |
|
{ |
|
// Skimming the surface. Forbid any movement on Z |
|
m_vCurrentVelocity.z = 0.0; |
|
} |
|
} |
|
else if( GetWaterLevel() > 0 ) |
|
{ |
|
// Allow the manhack to lift off, but not to go deeper. |
|
m_vCurrentVelocity.z = MAX( m_vCurrentVelocity.z, 0 ); |
|
} |
|
|
|
CheckCollisions(flInterval); |
|
|
|
// Blend out desired velocity when launched by the physcannon |
|
if ( HasPhysicsAttacker( MANHACK_SMASH_TIME ) && (!IsHeldByPhyscannon()) && VPhysicsGetObject() ) |
|
{ |
|
Vector vecCurrentVelocity; |
|
VPhysicsGetObject()->GetVelocity( &vecCurrentVelocity, NULL ); |
|
float flLerpFactor = (gpGlobals->curtime - m_flLastPhysicsInfluenceTime) / MANHACK_SMASH_TIME; |
|
flLerpFactor = clamp( flLerpFactor, 0.0f, 1.0f ); |
|
flLerpFactor = SimpleSplineRemapVal( flLerpFactor, 0.0f, 1.0f, 0.0f, 1.0f ); |
|
VectorLerp( vecCurrentVelocity, m_vCurrentVelocity, flLerpFactor, m_vCurrentVelocity ); |
|
} |
|
|
|
QAngle angles = GetLocalAngles(); |
|
|
|
// ------------------------------------------ |
|
// Stalling |
|
// ------------------------------------------ |
|
if (gpGlobals->curtime < m_flEngineStallTime) |
|
{ |
|
/* |
|
// If I'm stalled add random noise |
|
angles.x += -20+(random->RandomInt(-10,10)); |
|
angles.z += -20+(random->RandomInt(0,40)); |
|
|
|
TurnHeadRandomly(flInterval); |
|
*/ |
|
} |
|
else |
|
{ |
|
// Make frame rate independent |
|
float iRate = 0.5; |
|
float timeToUse = flInterval; |
|
while (timeToUse > 0) |
|
{ |
|
m_vCurrentBanking.x = (iRate * m_vCurrentBanking.x) + (1 - iRate)*(m_vTargetBanking.x); |
|
m_vCurrentBanking.z = (iRate * m_vCurrentBanking.z) + (1 - iRate)*(m_vTargetBanking.z); |
|
timeToUse = -0.1; |
|
} |
|
angles.x = m_vCurrentBanking.x; |
|
angles.z = m_vCurrentBanking.z; |
|
angles.y = 0; |
|
|
|
#if 0 |
|
// Using our steering if we're not otherwise affecting our panels |
|
if ( m_flEngineStallTime < gpGlobals->curtime && m_flBurstDuration < gpGlobals->curtime ) |
|
{ |
|
Vector delta( 10 * AngleDiff( m_vTargetBanking.x, m_vCurrentBanking.x ), -10 * AngleDiff( m_vTargetBanking.z, m_vCurrentBanking.z ), 0 ); |
|
//Vector delta( 3 * AngleNormalize( m_vCurrentBanking.x ), -4 * AngleNormalize( m_vCurrentBanking.z ), 0 ); |
|
VectorYawRotate( delta, -m_fHeadYaw, delta ); |
|
|
|
// DevMsg("%.0f %.0f\n", delta.x, delta.y ); |
|
|
|
SetPoseParameter( m_iPanel1, -delta.x - delta.y * 2); |
|
SetPoseParameter( m_iPanel2, -delta.x + delta.y * 2); |
|
SetPoseParameter( m_iPanel3, delta.x + delta.y * 2); |
|
SetPoseParameter( m_iPanel4, delta.x - delta.y * 2); |
|
|
|
//SetPoseParameter( m_iPanel1, -delta.x ); |
|
//SetPoseParameter( m_iPanel2, -delta.x ); |
|
//SetPoseParameter( m_iPanel3, delta.x ); |
|
//SetPoseParameter( m_iPanel4, delta.x ); |
|
} |
|
#endif |
|
} |
|
|
|
// SetLocalAngles( angles ); |
|
|
|
if( m_lifeState != LIFE_DEAD ) |
|
{ |
|
PlayFlySound(); |
|
// SpinBlades( flInterval ); |
|
// WalkMove( GetCurrentVelocity() * flInterval, MASK_NPCSOLID ); |
|
} |
|
|
|
// NDebugOverlay::Line( GetAbsOrigin(), GetAbsOrigin() + Vector(0, 0, -10 ), 0, 255, 0, true, 0.1); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::SpinBlades(float flInterval) |
|
{ |
|
if (!m_bBladesActive) |
|
{ |
|
SetBodygroup( MANHACK_BODYGROUP_BLADE, MANHACK_BODYGROUP_OFF ); |
|
SetBodygroup( MANHACK_BODYGROUP_BLUR, MANHACK_BODYGROUP_OFF ); |
|
m_flBladeSpeed = 0.0; |
|
m_flPlaybackRate = 1.0; |
|
return; |
|
} |
|
|
|
if ( IsFlyingActivity( GetActivity() ) ) |
|
{ |
|
// Blades may only ramp up while the engine is running |
|
if ( m_flEngineStallTime < gpGlobals->curtime ) |
|
{ |
|
if (m_flBladeSpeed < 10) |
|
{ |
|
m_flBladeSpeed = m_flBladeSpeed * 2 + 1; |
|
} |
|
else |
|
{ |
|
// accelerate engine |
|
m_flBladeSpeed = m_flBladeSpeed + 80 * flInterval; |
|
} |
|
} |
|
|
|
if (m_flBladeSpeed > 100) |
|
{ |
|
m_flBladeSpeed = 100; |
|
} |
|
|
|
// blend through blades, blades+blur, blur |
|
if (m_flBladeSpeed < 20) |
|
{ |
|
SetBodygroup( MANHACK_BODYGROUP_BLADE, MANHACK_BODYGROUP_ON ); |
|
SetBodygroup( MANHACK_BODYGROUP_BLUR, MANHACK_BODYGROUP_OFF ); |
|
} |
|
else if (m_flBladeSpeed < 40) |
|
{ |
|
SetBodygroup( MANHACK_BODYGROUP_BLADE, MANHACK_BODYGROUP_ON ); |
|
SetBodygroup( MANHACK_BODYGROUP_BLUR, MANHACK_BODYGROUP_ON ); |
|
} |
|
else |
|
{ |
|
SetBodygroup( MANHACK_BODYGROUP_BLADE, MANHACK_BODYGROUP_OFF ); |
|
SetBodygroup( MANHACK_BODYGROUP_BLUR, MANHACK_BODYGROUP_ON ); |
|
} |
|
|
|
m_flPlaybackRate = m_flBladeSpeed / 100.0; |
|
} |
|
else |
|
{ |
|
m_flBladeSpeed = 0.0; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Smokes and sparks, exploding periodically. Eventually it goes away. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::MoveExecute_Dead(float flInterval) |
|
{ |
|
if( GetWaterLevel() > 0 ) |
|
{ |
|
// No movement if sinking in water. |
|
return; |
|
} |
|
|
|
// Periodically emit smoke. |
|
if (gpGlobals->curtime > m_fSmokeTime && GetWaterLevel() == 0) |
|
{ |
|
// UTIL_Smoke(GetAbsOrigin(), random->RandomInt(10, 15), 10); |
|
m_fSmokeTime = gpGlobals->curtime + random->RandomFloat( 0.1, 0.3); |
|
} |
|
|
|
// Periodically emit sparks. |
|
if (gpGlobals->curtime > m_fSparkTime) |
|
{ |
|
g_pEffects->Sparks( GetAbsOrigin() ); |
|
m_fSparkTime = gpGlobals->curtime + random->RandomFloat(0.1, 0.3); |
|
} |
|
|
|
Vector newVelocity = GetCurrentVelocity(); |
|
|
|
// accelerate faster and faster when dying |
|
newVelocity = newVelocity + (newVelocity * 1.5 * flInterval ); |
|
|
|
// Lose lift |
|
newVelocity.z -= 0.02*flInterval*(GetCurrentGravity()); |
|
|
|
// ---------------------------------------------------------------------------------------- |
|
// Add in any forced velocity |
|
// ---------------------------------------------------------------------------------------- |
|
newVelocity += m_vForceVelocity; |
|
SetCurrentVelocity( newVelocity ); |
|
m_vForceVelocity = vec3_origin; |
|
|
|
|
|
// Lots of noise!! Out of control! |
|
AddNoiseToVelocity( 5.0 ); |
|
|
|
|
|
// ---------------------- |
|
// Limit overall speed |
|
// ---------------------- |
|
LimitSpeed( -1, MANHACK_MAX_SPEED * 2.0 ); |
|
|
|
QAngle angles = GetLocalAngles(); |
|
|
|
// ------------------------------------------ |
|
// If I'm dying, add random banking noise |
|
// ------------------------------------------ |
|
angles.x += -20+(random->RandomInt(0,40)); |
|
angles.z += -20+(random->RandomInt(0,40)); |
|
|
|
CheckCollisions(flInterval); |
|
PlayFlySound(); |
|
|
|
// SetLocalAngles( angles ); |
|
|
|
WalkMove( GetCurrentVelocity() * flInterval,MASK_NPCSOLID ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::Precache(void) |
|
{ |
|
// |
|
// Model. |
|
// |
|
PrecacheModel("models/manhack.mdl"); |
|
PrecacheModel( MANHACK_GLOW_SPRITE ); |
|
PropBreakablePrecacheAll( MAKE_STRING("models/manhack.mdl") ); |
|
|
|
PrecacheScriptSound( "NPC_Manhack.Die" ); |
|
PrecacheScriptSound( "NPC_Manhack.Bat" ); |
|
PrecacheScriptSound( "NPC_Manhack.Grind" ); |
|
PrecacheScriptSound( "NPC_Manhack.Slice" ); |
|
PrecacheScriptSound( "NPC_Manhack.EngineNoise" ); |
|
PrecacheScriptSound( "NPC_Manhack.Unpack" ); |
|
PrecacheScriptSound( "NPC_Manhack.ChargeAnnounce" ); |
|
PrecacheScriptSound( "NPC_Manhack.ChargeEnd" ); |
|
PrecacheScriptSound( "NPC_Manhack.Stunned" ); |
|
|
|
// Sounds used on Client: |
|
PrecacheScriptSound( "NPC_Manhack.EngineSound1" ); |
|
PrecacheScriptSound( "NPC_Manhack.EngineSound2" ); |
|
PrecacheScriptSound( "NPC_Manhack.BladeSound" ); |
|
|
|
BaseClass::Precache(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::GatherEnemyConditions( CBaseEntity *pEnemy ) |
|
{ |
|
// The Manhack "regroups" when its in attack range but to |
|
// far above or below its enemy. Set the start attack |
|
// condition if we are far enough away from the enemy |
|
// or at the correct height |
|
|
|
// Don't bother with Z if the enemy is in a vehicle |
|
float fl2DDist = 60.0f; |
|
float flZDist = 12.0f; |
|
|
|
if ( GetEnemy()->IsPlayer() && assert_cast< CBasePlayer * >(GetEnemy())->IsInAVehicle() ) |
|
{ |
|
flZDist = 24.0f; |
|
} |
|
|
|
if ((GetAbsOrigin() - pEnemy->GetAbsOrigin()).Length2D() > fl2DDist) |
|
{ |
|
SetCondition(COND_MANHACK_START_ATTACK); |
|
} |
|
else |
|
{ |
|
float targetZ = pEnemy->EyePosition().z; |
|
if (fabs(GetAbsOrigin().z - targetZ) < flZDist) |
|
{ |
|
SetCondition(COND_MANHACK_START_ATTACK); |
|
} |
|
} |
|
BaseClass::GatherEnemyConditions(pEnemy); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: For innate melee attack |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
int CNPC_Manhack::MeleeAttack1Conditions( float flDot, float flDist ) |
|
{ |
|
if ( GetEnemy() == NULL ) |
|
return COND_NONE; |
|
|
|
//TODO: We could also decide if we want to back up here |
|
if ( m_flNextBurstTime > gpGlobals->curtime ) |
|
return COND_NONE; |
|
|
|
float flMaxDist = 45; |
|
float flMinDist = 24; |
|
bool bEnemyInVehicle = GetEnemy()->IsPlayer() && assert_cast< CBasePlayer * >(GetEnemy())->IsInAVehicle(); |
|
if ( GetEnemy()->IsPlayer() && assert_cast< CBasePlayer * >(GetEnemy())->IsInAVehicle() ) |
|
{ |
|
flMinDist = 0; |
|
flMaxDist = 200.0f; |
|
} |
|
|
|
if (flDist > flMaxDist) |
|
{ |
|
return COND_TOO_FAR_TO_ATTACK; |
|
} |
|
|
|
if (flDist < flMinDist) |
|
{ |
|
return COND_TOO_CLOSE_TO_ATTACK; |
|
} |
|
|
|
// Check our current velocity and speed, if it's too far off, we need to settle |
|
|
|
// Don't bother with Z if the enemy is in a vehicle |
|
if ( bEnemyInVehicle ) |
|
{ |
|
return COND_CAN_MELEE_ATTACK1; |
|
} |
|
|
|
// Assume the this check is in regards to my current enemy |
|
// for the Manhacks spetial condition |
|
float deltaZ = GetAbsOrigin().z - GetEnemy()->EyePosition().z; |
|
if ( (deltaZ > 12.0f) || (deltaZ < -24.0f) ) |
|
{ |
|
return COND_TOO_CLOSE_TO_ATTACK; |
|
} |
|
|
|
return COND_CAN_MELEE_ATTACK1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pTask - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::RunTask( const Task_t *pTask ) |
|
{ |
|
switch ( pTask->iTask ) |
|
{ |
|
// Override this task so we go for the enemy at eye level |
|
case TASK_MANHACK_HOVER: |
|
{ |
|
break; |
|
} |
|
|
|
// If my enemy has moved significantly, update my path |
|
case TASK_WAIT_FOR_MOVEMENT: |
|
{ |
|
CBaseEntity *pEnemy = GetEnemy(); |
|
if (pEnemy && |
|
(GetCurSchedule()->GetId() == SCHED_CHASE_ENEMY) && |
|
GetNavigator()->IsGoalActive() ) |
|
{ |
|
Vector vecEnemyPosition; |
|
vecEnemyPosition = pEnemy->EyePosition(); |
|
if ( GetNavigator()->GetGoalPos().DistToSqr(vecEnemyPosition) > 40 * 40 ) |
|
{ |
|
GetNavigator()->UpdateGoalPos( vecEnemyPosition ); |
|
} |
|
} |
|
BaseClass::RunTask(pTask); |
|
break; |
|
} |
|
|
|
case TASK_MANHACK_MOVEAT_SAVEPOSITION: |
|
{ |
|
// do the movement thingy |
|
|
|
// NDebugOverlay::Line( GetAbsOrigin(), m_vSavePosition, 0, 255, 0, true, 0.1); |
|
|
|
Vector dir = (m_vSavePosition - GetAbsOrigin()); |
|
float dist = VectorNormalize( dir ); |
|
float t = m_fSwarmMoveTime - gpGlobals->curtime; |
|
|
|
if (t < 0.1) |
|
{ |
|
if (dist > 256) |
|
{ |
|
TaskFail( FAIL_NO_ROUTE ); |
|
} |
|
else |
|
{ |
|
TaskComplete(); |
|
} |
|
} |
|
else if (dist < 64) |
|
{ |
|
m_vSwarmMoveTarget = GetAbsOrigin() - Vector( -dir.y, dir.x, 0 ) * 4; |
|
} |
|
else |
|
{ |
|
m_vSwarmMoveTarget = GetAbsOrigin() + dir * 10; |
|
} |
|
break; |
|
} |
|
|
|
default: |
|
{ |
|
BaseClass::RunTask(pTask); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::Spawn(void) |
|
{ |
|
Precache(); |
|
|
|
#ifdef _XBOX |
|
// Always fade the corpse |
|
AddSpawnFlags( SF_NPC_FADE_CORPSE ); |
|
#endif // _XBOX |
|
|
|
SetModel( "models/manhack.mdl" ); |
|
SetHullType(HULL_TINY_CENTERED); |
|
SetHullSizeNormal(); |
|
|
|
SetSolid( SOLID_BBOX ); |
|
AddSolidFlags( FSOLID_NOT_STANDABLE ); |
|
|
|
if ( HasSpawnFlags( SF_MANHACK_CARRIED ) ) |
|
{ |
|
AddSolidFlags( FSOLID_NOT_SOLID ); |
|
SetMoveType( MOVETYPE_NONE ); |
|
} |
|
else |
|
{ |
|
SetMoveType( MOVETYPE_VPHYSICS ); |
|
} |
|
|
|
m_iHealth = sk_manhack_health.GetFloat(); |
|
SetViewOffset( Vector(0, 0, 10) ); // Position of the eyes relative to NPC's origin. |
|
m_flFieldOfView = VIEW_FIELD_FULL; |
|
m_NPCState = NPC_STATE_NONE; |
|
|
|
if ( m_spawnflags & SF_MANHACK_USE_AIR_NODES) |
|
{ |
|
SetNavType(NAV_FLY); |
|
} |
|
else |
|
{ |
|
SetNavType(NAV_GROUND); |
|
} |
|
|
|
AddEFlags( EFL_NO_DISSOLVE | EFL_NO_MEGAPHYSCANNON_RAGDOLL ); |
|
AddEffects( EF_NOSHADOW ); |
|
|
|
SetBloodColor( DONT_BLEED ); |
|
SetCurrentVelocity( vec3_origin ); |
|
m_vForceVelocity.Init(); |
|
m_vCurrentBanking.Init(); |
|
m_vTargetBanking.Init(); |
|
|
|
m_flNextBurstTime = gpGlobals->curtime; |
|
|
|
CapabilitiesAdd( bits_CAP_INNATE_MELEE_ATTACK1 | bits_CAP_MOVE_FLY | bits_CAP_SQUAD ); |
|
|
|
m_flNextEngineSoundTime = gpGlobals->curtime; |
|
m_flWaterSuspendTime = gpGlobals->curtime; |
|
m_flEngineStallTime = gpGlobals->curtime; |
|
m_fForceMoveTime = gpGlobals->curtime; |
|
m_vForceMoveTarget = vec3_origin; |
|
m_fSwarmMoveTime = gpGlobals->curtime; |
|
m_vSwarmMoveTarget = vec3_origin; |
|
m_nLastSpinSound = -1; |
|
|
|
m_fSmokeTime = 0; |
|
m_fSparkTime = 0; |
|
|
|
// Set the noise mod to huge numbers right now, in case this manhack starts out waiting for a script |
|
// for instance, we don't want him to bob whilst he's waiting for a script. This allows designers |
|
// to 'hide' manhacks in small places. (sjb) |
|
SetNoiseMod( MANHACK_NOISEMOD_HIDE, MANHACK_NOISEMOD_HIDE, MANHACK_NOISEMOD_HIDE ); |
|
|
|
// Start out with full power! |
|
m_fEnginePowerScale = GetMaxEnginePower(); |
|
|
|
// find panels |
|
m_iPanel1 = LookupPoseParameter( "Panel1" ); |
|
m_iPanel2 = LookupPoseParameter( "Panel2" ); |
|
m_iPanel3 = LookupPoseParameter( "Panel3" ); |
|
m_iPanel4 = LookupPoseParameter( "Panel4" ); |
|
|
|
m_fHeadYaw = 0; |
|
|
|
NPCInit(); |
|
|
|
// Manhacks are designed to slam into things, so don't take much damage from it! |
|
SetImpactEnergyScale( 0.001 ); |
|
|
|
// Manhacks get 30 seconds worth of free knowledge. |
|
GetEnemies()->SetFreeKnowledgeDuration( 30.0 ); |
|
|
|
// don't be an NPC, we want to collide with debris stuff |
|
SetCollisionGroup( COLLISION_GROUP_NONE ); |
|
|
|
m_bHeld = false; |
|
m_bHackedByAlyx = false; |
|
StopLoitering(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::StartEye( void ) |
|
{ |
|
//Create our Eye sprite |
|
if ( m_pEyeGlow == NULL ) |
|
{ |
|
m_pEyeGlow = CSprite::SpriteCreate( MANHACK_GLOW_SPRITE, GetLocalOrigin(), false ); |
|
m_pEyeGlow->SetAttachment( this, LookupAttachment( "Eye" ) ); |
|
|
|
if( m_bHackedByAlyx ) |
|
{ |
|
m_pEyeGlow->SetTransparency( kRenderTransAdd, 0, 255, 0, 128, kRenderFxNoDissipation ); |
|
m_pEyeGlow->SetColor( 0, 255, 0 ); |
|
} |
|
else |
|
{ |
|
m_pEyeGlow->SetTransparency( kRenderTransAdd, 255, 0, 0, 128, kRenderFxNoDissipation ); |
|
m_pEyeGlow->SetColor( 255, 0, 0 ); |
|
} |
|
|
|
m_pEyeGlow->SetBrightness( 164, 0.1f ); |
|
m_pEyeGlow->SetScale( 0.25f, 0.1f ); |
|
m_pEyeGlow->SetAsTemporary(); |
|
} |
|
|
|
//Create our light sprite |
|
if ( m_pLightGlow == NULL ) |
|
{ |
|
m_pLightGlow = CSprite::SpriteCreate( MANHACK_GLOW_SPRITE, GetLocalOrigin(), false ); |
|
m_pLightGlow->SetAttachment( this, LookupAttachment( "Light" ) ); |
|
|
|
if( m_bHackedByAlyx ) |
|
{ |
|
m_pLightGlow->SetTransparency( kRenderTransAdd, 0, 255, 0, 128, kRenderFxNoDissipation ); |
|
m_pLightGlow->SetColor( 0, 255, 0 ); |
|
} |
|
else |
|
{ |
|
m_pLightGlow->SetTransparency( kRenderTransAdd, 255, 0, 0, 128, kRenderFxNoDissipation ); |
|
m_pLightGlow->SetColor( 255, 0, 0 ); |
|
} |
|
|
|
m_pLightGlow->SetBrightness( 164, 0.1f ); |
|
m_pLightGlow->SetScale( 0.25f, 0.1f ); |
|
m_pLightGlow->SetAsTemporary(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CNPC_Manhack::Activate() |
|
{ |
|
BaseClass::Activate(); |
|
|
|
if ( IsAlive() ) |
|
{ |
|
StartEye(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the engine sound started. Unless we're not supposed to have it on yet! |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::PostNPCInit( void ) |
|
{ |
|
// SetAbsVelocity( vec3_origin ); |
|
m_bBladesActive = (m_spawnflags & (SF_MANHACK_PACKED_UP|SF_MANHACK_CARRIED)) ? false : true; |
|
BladesInit(); |
|
} |
|
|
|
void CNPC_Manhack::BladesInit() |
|
{ |
|
if( !m_bBladesActive ) |
|
{ |
|
// manhack is packed up, so has no power of its own. |
|
// don't start the engine sounds. |
|
// make us fall a little slower than we should, for visual's sake |
|
SetGravity( UTIL_ScaleForGravity( 400 ) ); |
|
|
|
SetActivity( ACT_IDLE ); |
|
} |
|
else |
|
{ |
|
bool engineSound = (m_spawnflags & SF_NPC_GAG) ? false : true; |
|
StartEngine( engineSound ); |
|
SetActivity( ACT_FLY ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Crank up the engine! |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::StartEngine( bool fStartSound ) |
|
{ |
|
if( fStartSound ) |
|
{ |
|
SoundInit(); |
|
} |
|
|
|
// Make the blade appear. |
|
SetBodygroup( 1, 1 ); |
|
|
|
// Pop up a little if falling fast! |
|
Vector vecVelocity; |
|
GetVelocity( &vecVelocity, NULL ); |
|
if( ( m_spawnflags & SF_MANHACK_PACKED_UP ) && vecVelocity.z < 0 ) |
|
{ |
|
// DevMsg(" POP UP \n" ); |
|
// ApplyAbsVelocityImpulse( Vector(0,0,-vecVelocity.z*0.75) ); |
|
} |
|
|
|
// Under powered flight now. |
|
// SetMoveType( MOVETYPE_STEP ); |
|
// SetGravity( MANHACK_GRAVITY ); |
|
AddFlag( FL_FLY ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Start the manhack's engine sound. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::SoundInit( void ) |
|
{ |
|
m_nEnginePitch1 = MANHACK_MIN_PITCH1; |
|
m_flEnginePitch1Time = gpGlobals->curtime; |
|
m_nEnginePitch2 = MANHACK_MIN_PITCH2; |
|
m_flEnginePitch2Time = gpGlobals->curtime; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::StopLoopingSounds(void) |
|
{ |
|
BaseClass::StopLoopingSounds(); |
|
m_nEnginePitch1 = -1; |
|
m_flEnginePitch1Time = gpGlobals->curtime; |
|
m_nEnginePitch2 = -1; |
|
m_flEnginePitch2Time = gpGlobals->curtime; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : pTask - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::StartTask( const Task_t *pTask ) |
|
{ |
|
switch (pTask->iTask) |
|
{ |
|
case TASK_MANHACK_UNPACK: |
|
{ |
|
// Just play a sound for now. |
|
EmitSound( "NPC_Manhack.Unpack" ); |
|
|
|
TaskComplete(); |
|
} |
|
break; |
|
|
|
case TASK_MANHACK_HOVER: |
|
break; |
|
|
|
case TASK_MOVE_TO_TARGET_RANGE: |
|
case TASK_GET_PATH_TO_GOAL: |
|
case TASK_GET_PATH_TO_ENEMY_LKP: |
|
case TASK_GET_PATH_TO_PLAYER: |
|
{ |
|
BaseClass::StartTask( pTask ); |
|
/* |
|
// FIXME: why were these tasks considered bad? |
|
_asm |
|
{ |
|
int 3; |
|
int 5; |
|
} |
|
*/ |
|
} |
|
break; |
|
|
|
case TASK_FACE_IDEAL: |
|
{ |
|
// this shouldn't ever happen, but if it does, don't choke |
|
TaskComplete(); |
|
} |
|
break; |
|
|
|
case TASK_GET_PATH_TO_ENEMY: |
|
{ |
|
if (IsUnreachable(GetEnemy())) |
|
{ |
|
TaskFail(FAIL_NO_ROUTE); |
|
return; |
|
} |
|
|
|
CBaseEntity *pEnemy = GetEnemy(); |
|
|
|
if ( pEnemy == NULL ) |
|
{ |
|
TaskFail(FAIL_NO_ENEMY); |
|
return; |
|
} |
|
|
|
if ( GetNavigator()->SetGoal( GOALTYPE_ENEMY ) ) |
|
{ |
|
TaskComplete(); |
|
} |
|
else |
|
{ |
|
// no way to get there =( |
|
DevWarning( 2, "GetPathToEnemy failed!!\n" ); |
|
RememberUnreachable(GetEnemy()); |
|
TaskFail(FAIL_NO_ROUTE); |
|
} |
|
break; |
|
} |
|
break; |
|
|
|
case TASK_GET_PATH_TO_TARGET: |
|
// DevMsg("TARGET\n"); |
|
BaseClass::StartTask( pTask ); |
|
break; |
|
|
|
case TASK_MANHACK_FIND_SQUAD_CENTER: |
|
{ |
|
if (!m_pSquad) |
|
{ |
|
m_vSavePosition = GetAbsOrigin(); |
|
TaskComplete(); |
|
break; |
|
} |
|
|
|
// calc center of squad |
|
int count = 0; |
|
m_vSavePosition = Vector( 0, 0, 0 ); |
|
|
|
// give attacking members more influence |
|
AISquadIter_t iter; |
|
for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) ) |
|
{ |
|
if (pSquadMember->HasStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 )) |
|
{ |
|
m_vSavePosition += pSquadMember->GetAbsOrigin() * 10; |
|
count += 10; |
|
} |
|
else |
|
{ |
|
m_vSavePosition += pSquadMember->GetAbsOrigin(); |
|
count++; |
|
} |
|
} |
|
|
|
// pull towards enemy |
|
if (GetEnemy() != NULL) |
|
{ |
|
m_vSavePosition += GetEnemyLKP() * 4; |
|
count += 4; |
|
} |
|
|
|
Assert( count != 0 ); |
|
m_vSavePosition = m_vSavePosition * (1.0 / count); |
|
|
|
TaskComplete(); |
|
} |
|
break; |
|
|
|
case TASK_MANHACK_FIND_SQUAD_MEMBER: |
|
{ |
|
if (m_pSquad) |
|
{ |
|
CAI_BaseNPC *pSquadMember = m_pSquad->GetAnyMember(); |
|
m_vSavePosition = pSquadMember->GetAbsOrigin(); |
|
|
|
// find attacking members |
|
AISquadIter_t iter; |
|
for (pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) ) |
|
{ |
|
// are they attacking? |
|
if (pSquadMember->HasStrategySlotRange( SQUAD_SLOT_ATTACK1, SQUAD_SLOT_ATTACK2 )) |
|
{ |
|
m_vSavePosition = pSquadMember->GetAbsOrigin(); |
|
break; |
|
} |
|
// do they have a goal? |
|
if (pSquadMember->GetNavigator()->IsGoalActive()) |
|
{ |
|
m_vSavePosition = pSquadMember->GetAbsOrigin(); |
|
break; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
m_vSavePosition = GetAbsOrigin(); |
|
} |
|
|
|
TaskComplete(); |
|
} |
|
break; |
|
|
|
case TASK_MANHACK_MOVEAT_SAVEPOSITION: |
|
{ |
|
trace_t tr; |
|
AI_TraceLine( GetAbsOrigin(), m_vSavePosition, MASK_NPCWORLDSTATIC, this, COLLISION_GROUP_NONE, &tr ); |
|
if (tr.DidHitWorld()) |
|
{ |
|
TaskFail( FAIL_NO_ROUTE ); |
|
} |
|
else |
|
{ |
|
m_fSwarmMoveTime = gpGlobals->curtime + RandomFloat( pTask->flTaskData * 0.8, pTask->flTaskData * 1.2 ); |
|
} |
|
} |
|
break; |
|
|
|
default: |
|
BaseClass::StartTask(pTask); |
|
break; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::UpdateOnRemove( void ) |
|
{ |
|
DestroySmokeTrail(); |
|
KillSprites( 0.0 ); |
|
BaseClass::UpdateOnRemove(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// 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_Manhack::HandleInteraction(int interactionType, void* data, CBaseCombatCharacter* sourceEnt) |
|
{ |
|
if (interactionType == g_interactionVortigauntClaw) |
|
{ |
|
// Freeze so vortigaunt and hit me easier |
|
|
|
m_vForceMoveTarget.x = ((Vector *)data)->x; |
|
m_vForceMoveTarget.y = ((Vector *)data)->y; |
|
m_vForceMoveTarget.z = ((Vector *)data)->z; |
|
m_fForceMoveTime = gpGlobals->curtime + 2.0; |
|
return false; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : float |
|
//----------------------------------------------------------------------------- |
|
float CNPC_Manhack::ManhackMaxSpeed( void ) |
|
{ |
|
if( m_flWaterSuspendTime > gpGlobals->curtime ) |
|
{ |
|
// Slower in water! |
|
return MANHACK_MAX_SPEED * 0.1; |
|
} |
|
|
|
if ( HasPhysicsAttacker( MANHACK_SMASH_TIME ) ) |
|
{ |
|
return MANHACK_NPC_BURST_SPEED; |
|
} |
|
|
|
return MANHACK_MAX_SPEED; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::ClampMotorForces( Vector &linear, AngularImpulse &angular ) |
|
{ |
|
float scale = m_flBladeSpeed / 100.0; |
|
|
|
// Msg("%.0f %.0f %.0f\n", linear.x, linear.y, linear.z ); |
|
|
|
float fscale = 3000 * scale; |
|
|
|
if ( m_flEngineStallTime > gpGlobals->curtime ) |
|
{ |
|
linear.x = 0.0f; |
|
linear.y = 0.0f; |
|
linear.z = clamp( linear.z, -fscale, fscale < 1200 ? 1200 : fscale ); |
|
} |
|
else |
|
{ |
|
// limit reaction forces |
|
linear.x = clamp( linear.x, -fscale, fscale ); |
|
linear.y = clamp( linear.y, -fscale, fscale ); |
|
linear.z = clamp( linear.z, -fscale, fscale < 1200 ? 1200 : fscale ); |
|
} |
|
|
|
angular.x *= scale; |
|
angular.y *= scale; |
|
angular.z *= scale; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::KillSprites( float flDelay ) |
|
{ |
|
if( m_pEyeGlow ) |
|
{ |
|
m_pEyeGlow->FadeAndDie( flDelay ); |
|
m_pEyeGlow = NULL; |
|
} |
|
|
|
if( m_pLightGlow ) |
|
{ |
|
m_pLightGlow->FadeAndDie( flDelay ); |
|
m_pLightGlow = NULL; |
|
} |
|
|
|
// Re-enable for light trails |
|
/* |
|
if ( m_hLightTrail ) |
|
{ |
|
m_hLightTrail->FadeAndDie( flDelay ); |
|
m_hLightTrail = NULL; |
|
} |
|
*/ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Tests whether we're above the target's feet but also below their top |
|
// Input : *pTarget - who we're testing against |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_Manhack::IsInEffectiveTargetZone( CBaseEntity *pTarget ) |
|
{ |
|
Vector vecMaxPos, vecMinPos; |
|
float ourHeight = WorldSpaceCenter().z; |
|
|
|
// If the enemy is in a vehicle, we need to get those bounds |
|
if ( pTarget && pTarget->IsPlayer() && assert_cast< CBasePlayer * >(pTarget)->IsInAVehicle() ) |
|
{ |
|
CBaseEntity *pVehicle = assert_cast< CBasePlayer * >(pTarget)->GetVehicleEntity(); |
|
pVehicle->CollisionProp()->NormalizedToWorldSpace( Vector(0.0f,0.0f,1.0f), &vecMaxPos ); |
|
pVehicle->CollisionProp()->NormalizedToWorldSpace( Vector(0.0f,0.0f,0.0f), &vecMinPos ); |
|
|
|
if ( ourHeight > vecMinPos.z && ourHeight < vecMaxPos.z ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
// Get the enemies top and bottom point |
|
pTarget->CollisionProp()->NormalizedToWorldSpace( Vector(0.0f,0.0f,1.0f), &vecMaxPos ); |
|
#ifdef _XBOX |
|
pTarget->CollisionProp()->NormalizedToWorldSpace( Vector(0.0f,0.0f,0.5f), &vecMinPos ); // Only half the body is valid |
|
#else |
|
pTarget->CollisionProp()->NormalizedToWorldSpace( Vector(0.0f,0.0f,0.0f), &vecMinPos ); |
|
#endif // _XBOX |
|
// See if we're within that range |
|
if ( ourHeight > vecMinPos.z && ourHeight < vecMaxPos.z ) |
|
return true; |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pEnemy - |
|
// &chasePosition - |
|
// &tolerance - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::TranslateNavGoal( CBaseEntity *pEnemy, Vector &chasePosition ) |
|
{ |
|
if ( pEnemy && pEnemy->IsPlayer() && assert_cast< CBasePlayer * >(pEnemy)->IsInAVehicle() ) |
|
{ |
|
Vector vecNewPos; |
|
CBaseEntity *pVehicle = assert_cast< CBasePlayer * >(pEnemy)->GetVehicleEntity(); |
|
pVehicle->CollisionProp()->NormalizedToWorldSpace( Vector(0.5,0.5,0.5f), &vecNewPos ); |
|
chasePosition.z = vecNewPos.z; |
|
} |
|
else |
|
{ |
|
Vector vecTarget; |
|
pEnemy->CollisionProp()->NormalizedToCollisionSpace( Vector(0,0,0.75f), &vecTarget ); |
|
chasePosition.z += vecTarget.z; |
|
} |
|
} |
|
|
|
float CNPC_Manhack::GetDefaultNavGoalTolerance() |
|
{ |
|
return GetHullWidth(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input that disables the manhack's swarm behavior |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::InputDisableSwarm( inputdata_t &inputdata ) |
|
{ |
|
m_bDoSwarmBehavior = false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : &inputdata - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::InputUnpack( inputdata_t &inputdata ) |
|
{ |
|
if ( HasSpawnFlags( SF_MANHACK_PACKED_UP ) == false ) |
|
return; |
|
|
|
SetCondition( COND_LIGHT_DAMAGE ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pPhysGunUser - |
|
// reason - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ) |
|
{ |
|
m_hPhysicsAttacker = pPhysGunUser; |
|
m_flLastPhysicsInfluenceTime = gpGlobals->curtime; |
|
|
|
if ( reason == PUNTED_BY_CANNON ) |
|
{ |
|
StopLoitering(); |
|
|
|
m_bHeld = false; |
|
|
|
// There's about to be a massive change in velocity. |
|
// Think immediately so we can do our slice traces, etc. |
|
SetNextThink( gpGlobals->curtime + 0.01f ); |
|
|
|
// Stall our engine for awhile |
|
m_flEngineStallTime = gpGlobals->curtime + 2.0f; |
|
SetEyeState( MANHACK_EYE_STATE_STUNNED ); |
|
} |
|
else |
|
{ |
|
// Suppress collisions between the manhack and the player; we're currently bumping |
|
// almost certainly because it's not purely a physics object. |
|
SetOwnerEntity( pPhysGunUser ); |
|
m_bHeld = true; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pPhysGunUser - |
|
// Reason - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason ) |
|
{ |
|
// Stop suppressing collisions between the manhack and the player |
|
SetOwnerEntity( NULL ); |
|
|
|
m_bHeld = false; |
|
|
|
if ( Reason == LAUNCHED_BY_CANNON ) |
|
{ |
|
m_hPhysicsAttacker = pPhysGunUser; |
|
m_flLastPhysicsInfluenceTime = gpGlobals->curtime; |
|
|
|
// There's about to be a massive change in velocity. |
|
// Think immediately so we can do our slice traces, etc. |
|
SetNextThink( gpGlobals->curtime + 0.01f ); |
|
|
|
// Stall our engine for awhile |
|
m_flEngineStallTime = gpGlobals->curtime + 2.0f; |
|
SetEyeState( MANHACK_EYE_STATE_STUNNED ); |
|
} |
|
else |
|
{ |
|
if( m_bHackedByAlyx && !GetEnemy() ) |
|
{ |
|
// If a hacked manhack is released in peaceable conditions, |
|
// just loiter, don't zip off. |
|
StartLoitering( GetAbsOrigin() ); |
|
} |
|
|
|
m_hPhysicsAttacker = NULL; |
|
m_flLastPhysicsInfluenceTime = 0; |
|
} |
|
} |
|
|
|
void CNPC_Manhack::StartLoitering( const Vector &vecLoiterPosition ) |
|
{ |
|
//Msg("Start Loitering\n"); |
|
|
|
m_vTargetBanking = vec3_origin; |
|
m_vecLoiterPosition = GetAbsOrigin(); |
|
m_vForceVelocity = vec3_origin; |
|
SetCurrentVelocity( vec3_origin ); |
|
} |
|
|
|
CBasePlayer *CNPC_Manhack::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; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Manhacks that have been hacked by Alyx get more engine power (fly faster) |
|
//----------------------------------------------------------------------------- |
|
float CNPC_Manhack::GetMaxEnginePower() |
|
{ |
|
if( m_bHackedByAlyx ) |
|
{ |
|
return 2.0f; |
|
} |
|
|
|
return 1.0f; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::UpdatePanels( void ) |
|
{ |
|
if ( m_flEngineStallTime > gpGlobals->curtime ) |
|
{ |
|
SetPoseParameter( m_iPanel1, random->RandomFloat( 0.0f, 90.0f ) ); |
|
SetPoseParameter( m_iPanel2, random->RandomFloat( 0.0f, 90.0f ) ); |
|
SetPoseParameter( m_iPanel3, random->RandomFloat( 0.0f, 90.0f ) ); |
|
SetPoseParameter( m_iPanel4, random->RandomFloat( 0.0f, 90.0f ) ); |
|
return; |
|
} |
|
|
|
float panelPosition = GetPoseParameter( m_iPanel1 ); |
|
|
|
if ( m_bShowingHostile ) |
|
{ |
|
panelPosition = 90.0f;//UTIL_Approach( 90.0f, panelPosition, 90.0f ); |
|
} |
|
else |
|
{ |
|
panelPosition = UTIL_Approach( 0.0f, panelPosition, 25.0f ); |
|
} |
|
|
|
//FIXME: If we're going to have all these be equal, there's no need for 4 poses.. |
|
SetPoseParameter( m_iPanel1, panelPosition ); |
|
SetPoseParameter( m_iPanel2, panelPosition ); |
|
SetPoseParameter( m_iPanel3, panelPosition ); |
|
SetPoseParameter( m_iPanel4, panelPosition ); |
|
|
|
//TODO: Make these waver randomly? |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : hostile - |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::ShowHostile( bool hostile /*= true*/) |
|
{ |
|
if ( m_bShowingHostile == hostile ) |
|
return; |
|
|
|
//TODO: Open the manhack panels or close them, depending on the state |
|
m_bShowingHostile = hostile; |
|
|
|
if ( hostile ) |
|
{ |
|
EmitSound( "NPC_Manhack.ChargeAnnounce" ); |
|
} |
|
else |
|
{ |
|
EmitSound( "NPC_Manhack.ChargeEnd" ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::StartBurst( const Vector &vecDirection ) |
|
{ |
|
if ( m_flBurstDuration > gpGlobals->curtime ) |
|
return; |
|
|
|
ShowHostile(); |
|
|
|
// Don't burst attack again for a couple seconds |
|
m_flNextBurstTime = gpGlobals->curtime + 2.0; |
|
m_flBurstDuration = gpGlobals->curtime + 1.0; |
|
|
|
// Save off where we were going towards and for how long |
|
m_vecBurstDirection = vecDirection; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::StopBurst( bool bInterruptSchedule /*= false*/ ) |
|
{ |
|
if ( m_flBurstDuration < gpGlobals->curtime ) |
|
return; |
|
|
|
ShowHostile( false ); |
|
|
|
// Stop our burst timers |
|
m_flNextBurstTime = gpGlobals->curtime + 2.0f; //FIXME: Skill level based |
|
m_flBurstDuration = gpGlobals->curtime - 0.1f; |
|
|
|
if ( bInterruptSchedule ) |
|
{ |
|
// We need to rethink our current schedule |
|
ClearSchedule( "Stopping burst" ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Manhack::SetEyeState( int state ) |
|
{ |
|
// Make sure we're active |
|
StartEye(); |
|
|
|
switch( state ) |
|
{ |
|
case MANHACK_EYE_STATE_STUNNED: |
|
{ |
|
if ( m_pEyeGlow ) |
|
{ |
|
//Toggle our state |
|
m_pEyeGlow->SetColor( 255, 128, 0 ); |
|
m_pEyeGlow->SetScale( 0.15f, 0.1f ); |
|
m_pEyeGlow->SetBrightness( 164, 0.1f ); |
|
m_pEyeGlow->m_nRenderFX = kRenderFxStrobeFast; |
|
} |
|
|
|
if ( m_pLightGlow ) |
|
{ |
|
m_pLightGlow->SetColor( 255, 128, 0 ); |
|
m_pLightGlow->SetScale( 0.15f, 0.1f ); |
|
m_pLightGlow->SetBrightness( 164, 0.1f ); |
|
m_pLightGlow->m_nRenderFX = kRenderFxStrobeFast; |
|
} |
|
|
|
EmitSound("NPC_Manhack.Stunned"); |
|
|
|
break; |
|
} |
|
|
|
case MANHACK_EYE_STATE_CHARGE: |
|
{ |
|
if ( m_pEyeGlow ) |
|
{ |
|
//Toggle our state |
|
if( m_bHackedByAlyx ) |
|
{ |
|
m_pEyeGlow->SetColor( 0, 255, 0 ); |
|
} |
|
else |
|
{ |
|
m_pEyeGlow->SetColor( 255, 0, 0 ); |
|
} |
|
|
|
m_pEyeGlow->SetScale( 0.25f, 0.5f ); |
|
m_pEyeGlow->SetBrightness( 164, 0.1f ); |
|
m_pEyeGlow->m_nRenderFX = kRenderFxNone; |
|
} |
|
|
|
if ( m_pLightGlow ) |
|
{ |
|
if( m_bHackedByAlyx ) |
|
{ |
|
m_pLightGlow->SetColor( 0, 255, 0 ); |
|
} |
|
else |
|
{ |
|
m_pLightGlow->SetColor( 255, 0, 0 ); |
|
} |
|
|
|
m_pLightGlow->SetScale( 0.25f, 0.5f ); |
|
m_pLightGlow->SetBrightness( 164, 0.1f ); |
|
m_pLightGlow->m_nRenderFX = kRenderFxNone; |
|
} |
|
|
|
break; |
|
} |
|
|
|
default: |
|
if ( m_pEyeGlow ) |
|
m_pEyeGlow->m_nRenderFX = kRenderFxNone; |
|
break; |
|
} |
|
} |
|
|
|
|
|
unsigned int CNPC_Manhack::PhysicsSolidMaskForEntity( void ) const |
|
{ |
|
unsigned int mask = BaseClass::PhysicsSolidMaskForEntity(); |
|
if ( m_bIgnoreClipbrushes ) |
|
{ |
|
mask &= ~CONTENTS_MONSTERCLIP; |
|
} |
|
return mask; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_Manhack::CreateVPhysics( void ) |
|
{ |
|
if ( HasSpawnFlags( SF_MANHACK_CARRIED ) ) |
|
return false; |
|
|
|
return BaseClass::CreateVPhysics(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// Schedules |
|
// |
|
//----------------------------------------------------------------------------- |
|
AI_BEGIN_CUSTOM_NPC( npc_manhack, CNPC_Manhack ) |
|
|
|
DECLARE_TASK( TASK_MANHACK_HOVER ); |
|
DECLARE_TASK( TASK_MANHACK_UNPACK ); |
|
DECLARE_TASK( TASK_MANHACK_FIND_SQUAD_CENTER ); |
|
DECLARE_TASK( TASK_MANHACK_FIND_SQUAD_MEMBER ); |
|
DECLARE_TASK( TASK_MANHACK_MOVEAT_SAVEPOSITION ); |
|
|
|
DECLARE_CONDITION( COND_MANHACK_START_ATTACK ); |
|
|
|
DECLARE_ACTIVITY( ACT_MANHACK_UNPACK ); |
|
|
|
//========================================================= |
|
// > SCHED_MANHACK_ATTACK_HOVER |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_MANHACK_ATTACK_HOVER, |
|
|
|
" Tasks" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_FLY" |
|
" TASK_MANHACK_HOVER 0" |
|
" " |
|
" Interrupts" |
|
" COND_TOO_FAR_TO_ATTACK" |
|
" COND_TOO_CLOSE_TO_ATTACK" |
|
" COND_NEW_ENEMY" |
|
" COND_ENEMY_DEAD" |
|
" COND_LIGHT_DAMAGE" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_ENEMY_OCCLUDED" |
|
); |
|
|
|
|
|
//========================================================= |
|
// > SCHED_MANHACK_ATTACK_HOVER |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_MANHACK_DEPLOY, |
|
|
|
" Tasks" |
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_MANHACK_UNPACK" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_FLY" |
|
" " |
|
// " Interrupts" |
|
); |
|
|
|
//========================================================= |
|
// > SCHED_MANHACK_REGROUP |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_MANHACK_REGROUP, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SET_TOLERANCE_DISTANCE 24" |
|
" TASK_STORE_ENEMY_POSITION_IN_SAVEPOSITION 0" |
|
" TASK_FIND_BACKAWAY_FROM_SAVEPOSITION 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" " |
|
" Interrupts" |
|
" COND_MANHACK_START_ATTACK" |
|
" COND_NEW_ENEMY" |
|
" COND_CAN_MELEE_ATTACK1" |
|
); |
|
|
|
|
|
|
|
//========================================================= |
|
// > SCHED_MANHACK_SWARN |
|
//========================================================= |
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_MANHACK_SWARM_IDLE, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_MANHACK_SWARM_FAILURE" |
|
" TASK_MANHACK_FIND_SQUAD_CENTER 0" |
|
" TASK_MANHACK_MOVEAT_SAVEPOSITION 5" |
|
" " |
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
" COND_SEE_ENEMY" |
|
" COND_SEE_FEAR" |
|
" COND_LIGHT_DAMAGE" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_SMELL" |
|
" COND_PROVOKED" |
|
" COND_GIVE_WAY" |
|
" COND_HEAR_PLAYER" |
|
" COND_HEAR_DANGER" |
|
" COND_HEAR_COMBAT" |
|
" COND_HEAR_BULLET_IMPACT" |
|
); |
|
|
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_MANHACK_SWARM, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_MANHACK_SWARM_FAILURE" |
|
" TASK_MANHACK_FIND_SQUAD_CENTER 0" |
|
" TASK_MANHACK_MOVEAT_SAVEPOSITION 1" |
|
" " |
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
" COND_CAN_MELEE_ATTACK1" |
|
" COND_LIGHT_DAMAGE" |
|
" COND_HEAVY_DAMAGE" |
|
); |
|
|
|
DEFINE_SCHEDULE |
|
( |
|
SCHED_MANHACK_SWARM_FAILURE, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_WAIT 2" |
|
" TASK_WAIT_RANDOM 2" |
|
" TASK_MANHACK_FIND_SQUAD_MEMBER 0" |
|
" TASK_GET_PATH_TO_SAVEPOSITION 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" " |
|
" Interrupts" |
|
" COND_SEE_ENEMY" |
|
" COND_NEW_ENEMY" |
|
); |
|
|
|
AI_END_CUSTOM_NPC()
|
|
|