From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/hl2/npc_manhack.cpp | 6856 ++++++++++++++++---------------- 1 file changed, 3428 insertions(+), 3428 deletions(-) (limited to 'mp/src/game/server/hl2/npc_manhack.cpp') 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(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(pHitEntity) || dynamic_cast(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(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(pHitEntity) || dynamic_cast(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() -- cgit v1.2.3