aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/npc_manhack.cpp
diff options
context:
space:
mode:
authorJørgen P. Tjernø <[email protected]>2013-12-02 19:31:46 -0800
committerJørgen P. Tjernø <[email protected]>2013-12-02 19:46:31 -0800
commitf56bb35301836e56582a575a75864392a0177875 (patch)
treede61ddd39de3e7df52759711950b4c288592f0dc /mp/src/game/server/hl2/npc_manhack.cpp
parentMark some more files as text. (diff)
downloadsource-sdk-2013-f56bb35301836e56582a575a75864392a0177875.tar.xz
source-sdk-2013-f56bb35301836e56582a575a75864392a0177875.zip
Fix line endings. WHAMMY.
Diffstat (limited to 'mp/src/game/server/hl2/npc_manhack.cpp')
-rw-r--r--mp/src/game/server/hl2/npc_manhack.cpp6856
1 files changed, 3428 insertions, 3428 deletions
diff --git a/mp/src/game/server/hl2/npc_manhack.cpp b/mp/src/game/server/hl2/npc_manhack.cpp
index 5dc22f8f..feef84f8 100644
--- a/mp/src/game/server/hl2/npc_manhack.cpp
+++ b/mp/src/game/server/hl2/npc_manhack.cpp
@@ -1,3428 +1,3428 @@
-//========= 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()
+//========= 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()