summaryrefslogtreecommitdiff
path: root/game/server/hl2/npc_hydra.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/hl2/npc_hydra.cpp')
-rw-r--r--game/server/hl2/npc_hydra.cpp1952
1 files changed, 1952 insertions, 0 deletions
diff --git a/game/server/hl2/npc_hydra.cpp b/game/server/hl2/npc_hydra.cpp
new file mode 100644
index 0000000..5818443
--- /dev/null
+++ b/game/server/hl2/npc_hydra.cpp
@@ -0,0 +1,1952 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+
+#include "npc_hydra.h"
+
+#include "ai_hull.h"
+#include "saverestore_utlvector.h"
+#include "physics_saverestore.h"
+#include "vphysics/constraints.h"
+#include "vcollide_parse.h"
+#include "ragdoll_shared.h"
+#include "physics_prop_ragdoll.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//-----------------------------------------------------------------------------
+//
+// CNPC_Hydra
+//
+
+#define HYDRA_MAX_LENGTH 500
+
+LINK_ENTITY_TO_CLASS( npc_hydra, CNPC_Hydra );
+
+//=========================================================
+// Hydra activities
+//=========================================================
+int ACT_HYDRA_COWER;
+int ACT_HYDRA_STAB;
+
+//=========================================================
+// Private conditions
+//=========================================================
+
+//==================================================
+// AntlionConditions
+//==================================================
+
+enum
+{
+ COND_HYDRA_SNAGGED = LAST_SHARED_CONDITION,
+ COND_HYDRA_STUCK,
+ COND_HYDRA_OVERSHOOT,
+ COND_HYDRA_OVERSTRETCH, // longer than max distance
+ COND_HYDRA_STRIKE, // head hit something
+ COND_HYDRA_NOSTUCK // no segments are stuck
+};
+
+//=========================================================
+// Hydra schedules
+//=========================================================
+enum
+{
+ SCHED_HYDRA_DEPLOY = LAST_SHARED_SCHEDULE,
+ SCHED_HYDRA_RETRACT,
+ SCHED_HYDRA_IDLE,
+ SCHED_HYDRA_STAB, // shoot out head and try to hit object
+ SCHED_HYDRA_PULLBACK, //
+ SCHED_HYDRA_TIGHTEN_SLACK, // snagged on something, tighten slack up to obstacle and try again from there
+ SCHED_HYDRA_RETREAT,
+ SCHED_HYDRA_THROW,
+ SCHED_HYDRA_RANGE_ATTACK
+};
+
+//=========================================================
+// Hydra tasks
+//=========================================================
+enum
+{
+ TASK_HYDRA_RETRACT = LAST_SHARED_TASK,
+ TASK_HYDRA_DEPLOY,
+ TASK_HYDRA_GET_OBJECT,
+ TASK_HYDRA_THROW_OBJECT,
+ TASK_HYDRA_PREP_STAB,
+ TASK_HYDRA_STAB,
+ TASK_HYDRA_PULLBACK,
+ TASK_HYDRA_SET_MAX_TENSION,
+ TASK_HYDRA_SET_BLEND_TENSION
+};
+
+
+//---------------------------------------------------------
+// Custom Client entity
+//---------------------------------------------------------
+IMPLEMENT_SERVERCLASS_ST(CNPC_Hydra, DT_NPC_Hydra)
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 0 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 1 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 2 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 3 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 4 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 5 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 6 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 7 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 8 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 9 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 10 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 11 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 12 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 13 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 14 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 15 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 16 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 17 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 18 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 19 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 20 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 21 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 22 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 23 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 24 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 25 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 26 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 27 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 28 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 29 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 30 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO_NETWORKARRAYELEM( m_vecChain, 31 ), -1, SPROP_COORD ),
+ SendPropVector( SENDINFO( m_vecHeadDir ), -1, SPROP_NORMAL ),
+ SendPropFloat( SENDINFO( m_flRelaxedLength ), 12, 0, 0.0, HYDRA_MAX_LENGTH * 1.5 ),
+END_SEND_TABLE()
+
+
+//---------------------------------------------------------
+// Save/Restore
+//---------------------------------------------------------
+BEGIN_DATADESC( CNPC_Hydra )
+
+ DEFINE_AUTO_ARRAY( m_vecChain, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_activeChain, FIELD_INTEGER ),
+ DEFINE_FIELD( m_bHasStuckSegments, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_flCurrentLength, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecHeadGoal, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_flHeadGoalInfluence, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecHeadDir, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flRelaxedLength, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecOutward, FIELD_VECTOR ),
+ DEFINE_UTLVECTOR( m_body, FIELD_EMBEDDED ),
+ DEFINE_FIELD( m_idealLength, FIELD_FLOAT ),
+ DEFINE_FIELD( m_idealSegmentLength, FIELD_FLOAT ),
+ DEFINE_FIELD( m_bExtendSoundActive, FIELD_BOOLEAN ),
+ DEFINE_SOUNDPATCH( m_pExtendTentacleSound ),
+ DEFINE_FIELD( m_seed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_vecTarget, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vecTargetDir, FIELD_VECTOR ),
+ DEFINE_FIELD( m_flLastAdjustmentTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flTaskStartTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flTaskEndTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flLengthTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bStabbedEntity, FIELD_BOOLEAN ),
+
+END_DATADESC()
+
+
+//-------------------------------------
+
+BEGIN_SIMPLE_DATADESC( HydraBone )
+ DEFINE_FIELD( vecPos, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( vecDelta, FIELD_VECTOR ),
+ DEFINE_FIELD( flIdealLength, FIELD_FLOAT ),
+ DEFINE_FIELD( flActualLength, FIELD_FLOAT ),
+ DEFINE_FIELD( bStuck, FIELD_BOOLEAN ),
+ DEFINE_FIELD( bOnFire, FIELD_BOOLEAN ),
+ DEFINE_FIELD( vecGoalPos, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( flGoalInfluence,FIELD_FLOAT ),
+END_DATADESC()
+
+//-------------------------------------
+
+static ConVar sv_hydraLength( "hydra_length", "100", FCVAR_ARCHIVE, "Hydra Length" );
+static ConVar sv_hydraSlack( "hydra_slack", "200", FCVAR_ARCHIVE, "Hydra Slack" );
+
+static ConVar sv_hydraSegmentLength( "hydra_segment_length", "30", FCVAR_ARCHIVE, "Hydra Slack" );
+
+static ConVar sv_hydraTest( "hydra_test", "1", FCVAR_ARCHIVE, "Hydra Slack" );
+
+static ConVar sv_hydraBendTension( "hydra_bend_tension", "0.4", FCVAR_ARCHIVE, "Hydra Slack" );
+static ConVar sv_hydraBendDelta( "hydra_bend_delta", "50", FCVAR_ARCHIVE, "Hydra Slack" );
+
+static ConVar sv_hydraGoalTension( "hydra_goal_tension", "0.5", FCVAR_ARCHIVE, "Hydra Slack" );
+static ConVar sv_hydraGoalDelta( "hydra_goal_delta", "400", FCVAR_ARCHIVE, "Hydra Slack" );
+
+static ConVar sv_hydraMomentum( "hydra_momentum", "0.5", FCVAR_ARCHIVE, "Hydra Slack" );
+
+static ConVar sv_hydraTestSpike( "sv_hydraTestSpike", "1", 0, "Hydra Test impaling code" );
+
+//-------------------------------------
+// Purpose: Initialize the custom schedules
+//-------------------------------------
+
+
+//-------------------------------------
+
+void CNPC_Hydra::Precache()
+{
+ PrecacheModel( "models/Hydra.mdl" );
+ UTIL_PrecacheOther( "hydra_impale" );
+
+ PrecacheScriptSound( "NPC_Hydra.ExtendTentacle" );
+
+ BaseClass::Precache();
+}
+
+
+void CNPC_Hydra::Activate( void )
+{
+ CPASAttenuationFilter filter( this );
+ CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ m_pExtendTentacleSound = controller.SoundCreate( filter, entindex(), "NPC_Hydra.ExtendTentacle" );
+
+ controller.Play( m_pExtendTentacleSound, 1.0, 100 );
+
+ BaseClass::Activate();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns this monster's place in the relationship table.
+//-----------------------------------------------------------------------------
+Class_T CNPC_Hydra::Classify( void )
+{
+ return CLASS_BARNACLE;
+}
+
+//-------------------------------------
+
+#define HYDRA_OUTWARD_BIAS 16
+#define HYDRA_INWARD_BIAS 30
+
+void CNPC_Hydra::Spawn()
+{
+ Precache();
+
+ BaseClass::Spawn();
+
+ SetModel( "models/Hydra.mdl" );
+
+ SetHullType(HULL_HUMAN);
+ SetHullSizeNormal();
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ SetMoveType( MOVETYPE_STEP );
+ SetBloodColor( BLOOD_COLOR_RED );
+ ClearEffects();
+ m_iHealth = 20;
+ m_flFieldOfView = -1.0;// indicates the width of this NPC's forward view cone ( as a dotproduct result )
+ m_NPCState = NPC_STATE_NONE;
+
+ GetVectors( NULL, NULL, &m_vecOutward );
+
+ SetAbsAngles( QAngle( 0, 0, 0 ) );
+
+ m_vecChain.Set( 0, GetAbsOrigin( ) - m_vecOutward * 32 );
+ m_vecChain.Set( 1, GetAbsOrigin( ) + m_vecOutward * 16 );
+
+ m_vecHeadGoal = m_vecChain[1] + m_vecOutward * HYDRA_OUTWARD_BIAS;
+ m_vecHeadDir = Vector( 0, 0, 1 );
+
+ // init bones
+ HydraBone bone;
+ bone.vecPos = GetAbsOrigin( ) - m_vecOutward * HYDRA_INWARD_BIAS;
+ m_body.AddToTail( bone );
+ bone.vecPos = m_vecChain[1];
+ m_body.AddToTail( bone );
+ bone.vecPos = m_vecHeadGoal;
+ m_body.AddToTail( bone );
+ bone.vecPos = m_vecHeadGoal + m_vecHeadDir;
+ m_body.AddToTail( bone );
+
+ m_idealSegmentLength = sv_hydraSegmentLength.GetFloat();
+
+ for (int i = 2; i < CHAIN_LINKS; i++)
+ {
+ m_vecChain.Set( i, m_vecChain[i-1] );
+ }
+
+ m_seed = random->RandomFloat( 0.0, 2000.0 );
+
+ NPCInit();
+
+ m_takedamage = DAMAGE_NO;
+}
+
+
+//-------------------------------------
+
+
+void CNPC_Hydra::RunAI( void )
+{
+ CheckLength( );
+
+ AdjustLength( );
+
+ BaseClass::RunAI();
+
+ CalcGoalForces( );
+ MoveBody( );
+
+ int i;
+ for (i = 1; i < CHAIN_LINKS && i < m_body.Count(); i++)
+ {
+ m_vecChain.Set( i, m_body[i].vecPos );
+
+#if 0
+ if (m_body[i].bStuck)
+ {
+ NDebugOverlay::Box(m_body[i].vecPos, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255, 0, 0, 20, .1);
+ }
+ else
+ {
+ NDebugOverlay::Box(m_body[i].vecPos, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 0, 255, 0, 20, .1);
+ }
+ NDebugOverlay::Line( m_body[i].vecPos, m_body[i].vecPos + m_body[i].vecDelta, 0, 255, 0, true, .1);
+ NDebugOverlay::Line( m_body[i-1].vecPos, m_body[i].vecPos, 255, 255, 255, true, .1);
+#endif
+
+#if 0
+ char text[128];
+ Q_snprintf( text, sizeof( text ), "%d", i );
+ NDebugOverlay::Text( m_body[i].vecPos, text, false, 0.1 );
+#endif
+
+#if 0
+ char text[128];
+ Q_snprintf( text, sizeof( text ), "%4.0f", (m_body[i].vecPos - m_body[i-1].vecPos).Length() * 100 / m_idealSegmentLength - 100);
+ NDebugOverlay::Text( 0.5*(m_body[i-1].vecPos + m_body[i].vecPos), text, false, 0.1 );
+#endif
+ }
+ //NDebugOverlay::Box(m_body[i].vecPos, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 0, 255, 0, 20, .1);
+ //NDebugOverlay::Box( m_vecHeadGoal, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255, 255, 0, 20, .1);
+ for (; i < CHAIN_LINKS; i++)
+ {
+ m_vecChain.Set( i, m_vecChain[i-1] );
+ }
+}
+
+
+
+
+Vector CNPC_Hydra::TestPosition( float t )
+{
+ // return GetAbsOrigin( ) + Vector( sin( (m_seed + t) * 2.3 ) * 15, cos( (m_seed + t) * 2.4 ) * 150, sin( ( m_seed + t ) * 1.8 ) * 50 ) + m_vecOutward * sv_hydraLength.GetFloat();;
+ t = (int)(t * 0.2);
+#if 1
+ Vector tmp = Vector( sin( (m_seed + t) * 0.8 ) * 15, cos( (m_seed + t) * 0.9 ) * 150, sin( ( m_seed + t ) * 0.4 ) * 50 );
+ tmp += Vector( sin( (m_seed + t) * 1.0 ) * 4, cos( (m_seed + t) * 0.9 ) * 4, sin( ( m_seed + t ) * 1.1 ) * 6 );
+ tmp += GetAbsOrigin( ) + m_vecOutward * sv_hydraLength.GetFloat();
+ return tmp;
+#else
+
+ Vector tmp;
+ tmp.Init;
+ CBaseEntity *pPlayer = (CBaseEntity *)UTIL_GetLocalPlayer();
+ if ( pPlayer )
+ {
+ tmp = pPlayer->EyePosition( );
+
+ Vector delta = (tmp - GetAbsOrigin( ));
+
+ if (delta.Length() > 200)
+ {
+ tmp = GetAbsOrigin( ) + Vector( 0, 0, 200 );
+ }
+ m_vecHeadDir = (pPlayer->EyePosition( ) - m_body[m_body.Count()-2].vecPos);
+ VectorNormalize( m_vecHeadDir );
+ }
+
+ return tmp;
+#endif
+ // m_vecHeadGoal = GetAbsOrigin( ) + Vector( sin( gpGlobals->curtime * 0.3 ) * 15, cos( gpGlobals->curtime * 0.4 ) * 150, sin( gpGlobals->curtime * 0.2 ) * 50 + dt );
+}
+
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculate the bone forces based on goal positions, bending rules, stretching rules, etc.
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+void CNPC_Hydra::CalcGoalForces( )
+{
+ int i;
+
+ int iFirst = 2;
+ int iLast = m_body.Count() - 1;
+
+ // keep head segment straight
+ m_body[iLast].vecGoalPos = m_vecHeadGoal; // + m_vecHeadDir * m_body[iLast-1].flActualLength;
+ m_body[iLast].flGoalInfluence = m_flHeadGoalInfluence;
+
+ m_body[iLast-1].vecGoalPos = m_vecHeadGoal - m_vecHeadDir * m_idealSegmentLength;
+ m_body[iLast-1].flGoalInfluence = 1.0; // m_flHeadGoalInfluence;
+
+
+ // momentum?
+ for (i = iFirst; i <= iLast; i++)
+ {
+ m_body[i].vecDelta = m_body[i].vecDelta * sv_hydraMomentum.GetFloat();
+ }
+
+ //Vector right, up;
+ //VectorVectors( m_vecHeadDir, right, up );
+
+ float flGoalSegmentLength = m_idealSegmentLength * ( m_idealLength / m_flCurrentLength);
+
+ // goal forces
+#if 1
+ for (i = iFirst; i <= iLast; i++)
+ {
+ // DevMsg("(%d) %.2f\n", i, t );
+
+ float flInfluence = m_body[i].flGoalInfluence;
+ if (flInfluence > 0)
+ {
+ m_body[i].flGoalInfluence = 0.0;
+
+ Vector v0 = (m_body[i].vecGoalPos - m_body[i].vecPos);
+ float length = v0.Length();
+ if (length > sv_hydraGoalDelta.GetFloat())
+ {
+ v0 = v0 * sv_hydraGoalDelta.GetFloat() / length;
+ }
+ m_body[i].vecDelta += v0 * flInfluence * sv_hydraGoalTension.GetFloat();
+ // NDebugOverlay::Box(m_body[i].vecGoalPos, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 255, 255, 0, flInfluence * 255, .1);
+ }
+ }
+#endif
+
+ // bending forces
+ for (i = iFirst-1; i <= iLast - 1; i++)
+ {
+ // DevMsg("(%d) %.2f\n", i, t );
+ Vector v3 = m_body[i+1].vecPos - m_body[i-1].vecPos;
+ VectorNormalize( v3 );
+
+ Vector delta;
+ float length;
+
+ //NDebugOverlay::Line( m_body[i].vecPos + v3 * flGoalSegmentLength, m_body[i].vecPos - v3 * flGoalSegmentLength, 255, 0, 0, true, .1);
+
+ if (i+1 <= iLast)
+ {
+ // towards head
+ delta = (m_body[i].vecPos + v3 * flGoalSegmentLength - m_body[i+1].vecPos) * sv_hydraBendTension.GetFloat();
+ length = delta.Length();
+ if (length > sv_hydraBendDelta.GetFloat())
+ {
+ delta = delta * (sv_hydraBendDelta.GetFloat() / length);
+ }
+ m_body[i+1].vecDelta += delta;
+ //NDebugOverlay::Line( m_body[i+1].vecPos, m_body[i+1].vecPos + delta, 255, 0, 0, true, .1);
+ }
+
+ if (i-1 >= iFirst)
+ {
+ // towards tail
+ delta = (m_body[i].vecPos - v3 * flGoalSegmentLength - m_body[i-1].vecPos) * sv_hydraBendTension.GetFloat();
+ length = delta.Length();
+ if (length > sv_hydraBendDelta.GetFloat())
+ {
+ delta = delta * (sv_hydraBendDelta.GetFloat() / length);
+ }
+ m_body[i-1].vecDelta += delta * 0.8;
+ //NDebugOverlay::Line( m_body[i-1].vecPos, m_body[i-1].vecPos + delta, 255, 0, 0, true, .1);
+ }
+ }
+
+ m_body[0].vecDelta = Vector( 0, 0, 0 );
+ m_body[1].vecDelta = Vector( 0, 0, 0 );
+
+ // normal gravity forces
+ for (i = iFirst; i <= iLast; i++)
+ {
+ if (!m_body[i].bStuck)
+ {
+ m_body[i].vecDelta.z -= 3.84 * 0.2;
+ }
+ }
+
+#if 0
+ // move delta's back toward the root
+ for (i = iLast; i > iFirst; i--)
+ {
+ Vector tmp = m_body[i].vecDelta;
+
+ m_body[i].vecDelta = tmp * 0.8;
+ m_body[i-1].vecDelta += tmp * 0.2;
+ }
+#endif
+
+ // prevent stretching
+ int maxChecks = m_body.Count() * 4;
+ i = iLast;
+ while (i > iFirst && maxChecks > 0)
+ {
+ bool didStretch = false;
+ Vector stretch = (m_body[i].vecPos + m_body[i].vecDelta) - (m_body[i-1].vecPos + m_body[i-1].vecDelta);
+ float t = VectorNormalize( stretch );
+ if (t > flGoalSegmentLength)
+ {
+ float f0 = DotProduct( m_body[i].vecDelta, stretch );
+ float f1 = DotProduct( m_body[i-1].vecDelta, stretch );
+ if (f0 > 0 && f0 > f1)
+ {
+ // Vector limit = stretch * (f0 - flGoalSegmentLength);
+ Vector limit = stretch * (t - flGoalSegmentLength);
+ // propagate pulling back down the chain
+ m_body[i].vecDelta -= limit * 0.5;
+ m_body[i-1].vecDelta += limit * 0.5;
+ didStretch = true;
+ }
+ }
+ if (didStretch)
+ {
+ if (i < iLast)
+ {
+ i++;
+ }
+ }
+ else
+ {
+ i--;
+ }
+ maxChecks--;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Move the body, check for collisions
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+void CNPC_Hydra::MoveBody( )
+{
+ int i;
+
+ int iFirst = 2;
+ int iLast = m_body.Count() - 1;
+
+ // clear stuck flags
+ for (i = 0; i <= iLast; i++)
+ {
+ m_body[i].bStuck = false;
+ }
+
+ // try to move all the nodes
+ for (i = iFirst; i <= iLast; i++)
+ {
+ trace_t tr;
+
+ // check direct movement
+ AI_TraceHull(m_body[i].vecPos, m_body[i].vecPos + m_body[i].vecDelta,
+ Vector( -2, -2, -2 ), Vector( 2, 2, 2 ),
+ MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
+
+ Vector direct = tr.endpos;
+ Vector delta = Vector( 0, 0, 0 );
+
+ Vector slide = m_body[i].vecDelta;
+ if (tr.fraction != 1.0)
+ {
+ // slow down and remove all motion in the direction of the plane
+ direct += tr.plane.normal;
+ Vector impactSpeed = (slide * tr.plane.normal) * tr.plane.normal;
+
+ slide = (slide - impactSpeed) * 0.8;
+
+ if (tr.m_pEnt)
+ {
+ if (i == iLast)
+ {
+ Stab( tr.m_pEnt, impactSpeed, tr );
+ }
+ else
+ {
+ Nudge( tr.m_pEnt, direct, impactSpeed );
+ }
+ }
+
+ // slow down and remove all motion in the direction of the plane
+ slide = (slide - (slide * tr.plane.normal) * tr.plane.normal) * 0.8;
+
+ // try to move the remaining distance anyways
+ AI_TraceHull(direct, direct + slide * (1 - tr.fraction),
+ Vector( -2, -2, -2 ), Vector( 2, 2, 2 ),
+ MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
+
+ // NDebugOverlay::Line( m_body[i].vecPos, tr.endpos, 255, 255, 0, true, 1);
+
+ direct = tr.endpos;
+
+ m_body[i].bStuck = true;
+
+ }
+
+ // make sure the new segment doesn't intersect the world
+ AI_TraceHull(direct, m_body[i-1].vecPos,
+ Vector( -2, -2, -2 ), Vector( 2, 2, 2 ),
+ MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
+
+ if (tr.fraction == 1.0)
+ {
+ if (i+1 < iLast)
+ {
+ AI_TraceHull(direct, m_body[i+1].vecPos,
+ Vector( -2, -2, -2 ), Vector( 2, 2, 2 ),
+ MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
+ }
+
+ if (tr.fraction == 1.0)
+ {
+ m_body[i].vecPos = direct;
+ delta = slide;
+ }
+ else
+ {
+ // FIXME: compute nudge force
+ m_body[i].bStuck = true;
+ //m_body[i+1].bStuck = true;
+ }
+ }
+ else
+ {
+ // FIXME: compute nudge force
+ m_body[i].bStuck = true;
+ //m_body[i-1].bStuck = true;
+ }
+
+ // m_body[i-1].vecDelta += (m_body[i].vecDelta - delta) * 0.25;
+ // m_body[i+1].vecDelta += (m_body[i].vecDelta - delta) * 0.25;
+ m_body[i].vecDelta = delta;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Push physics objects around if they get hit
+// Input : vecContact = point in space where contact supposidly happened
+// vecSpeed = in/sec of contact
+// Output :
+//-----------------------------------------------------------------------------
+
+void CNPC_Hydra::Nudge( CBaseEntity *pOther, const Vector &vecContact, const Vector &vecSpeed )
+{
+ if ( pOther->GetMoveType() != MOVETYPE_VPHYSICS )
+ {
+ return;
+ }
+
+ IPhysicsObject *pOtherPhysics = pOther->VPhysicsGetObject();
+
+ // Put the force on the line between the "contact point" and hit object origin
+ //Vector posOther;
+ //pOtherPhysics->GetPosition( &posOther, NULL );
+
+ // force is a 30kg object going 100 in/s
+ pOtherPhysics->ApplyForceOffset( vecSpeed * 30, vecContact );
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Push physics objects around if they get hit
+// Input : vecContact = point in space where contact supposidly happened
+// vecSpeed = in/sec of contact
+// Output :
+//-----------------------------------------------------------------------------
+
+void CNPC_Hydra::Stab( CBaseEntity *pOther, const Vector &vecSpeed, trace_t &tr )
+{
+ if (pOther->m_takedamage == DAMAGE_YES && !pOther->IsPlayer())
+ {
+ Vector dir = vecSpeed;
+ VectorNormalize( dir );
+
+ if ( !sv_hydraTestSpike.GetInt() )
+ {
+ ClearMultiDamage();
+ // FIXME: this is bogus
+ CTakeDamageInfo info( this, this, pOther->m_iHealth+25, DMG_SLASH );
+ CalculateMeleeDamageForce( &info, dir, tr.endpos );
+ pOther->DispatchTraceAttack( info, dir, &tr );
+ ApplyMultiDamage();
+ }
+ else
+ {
+ CBaseAnimating *pAnimating = dynamic_cast<CBaseAnimating *>(pOther);
+ if ( pAnimating )
+ {
+ AttachStabbedEntity( pAnimating, vecSpeed * 30, tr );
+ }
+ }
+ }
+ else
+ {
+ Nudge( pOther, tr.endpos, vecSpeed );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : vecContact = point in space where contact supposidly happened
+// vecSpeed = in/sec of contact
+// Output :
+//-----------------------------------------------------------------------------
+
+void CNPC_Hydra::Kick( CBaseEntity *pHitEntity, const Vector &vecContact, const Vector &vecSpeed )
+{
+
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : vecContact = point in space where contact supposidly happened
+// vecSpeed = in/sec of contact
+// Output :
+//-----------------------------------------------------------------------------
+
+void CNPC_Hydra::Splash( const Vector &vecSplashPos )
+{
+
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculate the actual hydra length
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+void CNPC_Hydra::CheckLength( )
+{
+ int i;
+
+ ClearCondition( COND_HYDRA_SNAGGED );
+ ClearCondition( COND_HYDRA_NOSTUCK );
+ ClearCondition( COND_HYDRA_OVERSTRETCH );
+
+ m_bHasStuckSegments = m_body[m_body.Count() - 1].bStuck;
+ m_flCurrentLength = 0;
+
+ for (i = 1; i < m_body.Count() - 1; i++)
+ {
+ float length = (m_body[i+1].vecPos - m_body[i].vecPos).Length();
+
+ Assert( m_body[i+1].vecPos.IsValid( ) );
+ Assert( m_body[i].vecPos.IsValid( ) );
+
+ Assert( IsFinite( length ) );
+
+ m_body[i].flActualLength = length;
+
+ m_flCurrentLength += length;
+
+ // check for over streatched segements
+ if (length > m_idealSegmentLength * 3.0 && (m_body[i].bStuck || m_body[i+1].bStuck))
+ {
+ //NDebugOverlay::Line( m_body[i].vecPos, m_body[i+1].vecPos, 255, 0, 0, true, 1.0);
+ SetCondition( COND_HYDRA_SNAGGED );
+ }
+ if (m_body[i].bStuck)
+ {
+ m_bHasStuckSegments = true;
+ }
+ }
+
+ if (m_flCurrentLength > HYDRA_MAX_LENGTH) // FIXME
+ {
+ SetCondition( COND_HYDRA_OVERSTRETCH );
+ }
+
+ if (!m_bHasStuckSegments)
+ {
+ SetCondition( COND_HYDRA_NOSTUCK );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Grow or shrink the hydra, as needed
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+void CNPC_Hydra::AdjustLength( )
+{
+ m_body[0].vecPos = m_body[1].vecPos - m_vecOutward * m_idealSegmentLength ;
+
+ // DevMsg( "actual %.0f ideal %.0f relaxed %.0f\n", actualLength, m_idealLength, m_idealSegmentLength * (m_body.Count() - 3) );
+
+ CalcRelaxedLength( );
+
+ // "NPC_Hydra.ExtendTentacle"
+
+ bool bAdjustFailed = false;
+ bool bShouldAdjust = false;
+
+ if (m_flCurrentLength < m_idealLength)
+ {
+ if (m_flRelaxedLength + m_idealSegmentLength * 0.5 < m_idealLength)
+ {
+ bShouldAdjust = true;
+ //if (!GrowFromMostStretched( ))
+ if (!GrowFromVirtualRoot())
+ {
+ bAdjustFailed = true;
+ }
+ }
+ }
+ else if (m_flCurrentLength > m_idealLength)
+ {
+ // if (relaxedLength > actualLength)
+ if (m_flRelaxedLength - m_idealSegmentLength * 0.5 > m_idealLength || HasCondition( COND_HYDRA_SNAGGED ))
+ {
+ bShouldAdjust = true;
+ if (!ContractFromRoot())
+ {
+ if (!ContractBetweenStuckSegments())
+ {
+ if (!ContractFromHead())
+ {
+ bAdjustFailed = true;
+ }
+ }
+ }
+ }
+ else if (gpGlobals->curtime - m_flLastAdjustmentTime > 1.0)
+ {
+ bShouldAdjust = true;
+ // start to panic
+ if (!GrowFromMostStretched( ))
+ {
+ bAdjustFailed = true;
+ }
+
+ // SplitLongestSegment( );
+ /*
+ if (!ContractBetweenStuckSegments())
+ {
+ if (!ContractFromHead())
+ {
+
+ }
+ }
+ */
+ }
+ else
+ {
+ bAdjustFailed = true;
+ }
+ }
+
+ if (!bAdjustFailed)
+ {
+ m_flLastAdjustmentTime = gpGlobals->curtime;
+ if (bShouldAdjust && !m_bExtendSoundActive)
+ {
+ m_bExtendSoundActive = true;
+ //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ //controller.SoundChangeVolume( m_pExtendTentacleSound, 1.0, 0.1 );
+ }
+ }
+ else if (bShouldAdjust)
+ {
+ m_bExtendSoundActive = false;
+ //CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
+ //controller.SoundChangeVolume( m_pExtendTentacleSound, 0.0, 0.3 );
+ }
+
+ CalcRelaxedLength( );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Remove nodes, starting at the end, regardless of length
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+bool CNPC_Hydra::ContractFromHead( )
+{
+ if (m_body.Count() <= 2)
+ {
+ return false;
+ }
+
+ int iNode = m_body.Count() - 1;
+
+ if (m_body[iNode].bStuck && m_body[iNode-1].flActualLength > m_idealSegmentLength * 2.0)
+ {
+ AddNodeBefore( iNode );
+ iNode = m_body.Count() - 1;
+ }
+
+ if (m_body.Count() <= 3)
+ {
+ return false;
+ }
+
+ // always legal since no new link is being formed
+
+ m_body.Remove( iNode );
+
+ CalcRelaxedLength( );
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Starting at the first stuck node back from the head, find a node to remove
+// between it and the actual root who is part of a chain that isn't too long.
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+bool CNPC_Hydra::ContractBetweenStuckSegments( )
+{
+ if (m_body.Count() <= 3)
+ return false;
+
+ // first first stuck segment closest to head;
+ int iStuckHead = VirtualRoot( );
+ if (iStuckHead < 3)
+ return false;
+
+ // find a non stuck node with the shortest distance between its neighbors
+ int iShortest = -1;
+ float dist = m_idealSegmentLength * 2;
+ int i;
+ for (i = iStuckHead - 1; i > 2; i--)
+ {
+ if (!m_body[i].bStuck)
+ {
+ float length = (m_body[i-1].vecPos - m_body[i+1].vecPos).Length();
+ // check segment length
+ if (length < dist )
+ {
+ if (IsValidConnection( i-1, i+1 ))
+ {
+ dist = length;
+ iShortest = i;
+ }
+ }
+ }
+ }
+ if (iShortest = -1)
+ return false;
+
+ // FIXME: check for tunneling
+ m_body.Remove( iShortest );
+
+ CalcRelaxedLength( );
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Try to remove segment closest to root
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+bool CNPC_Hydra::ContractFromRoot( )
+{
+ if (m_body.Count() <= 3)
+ return false;
+
+ // don't contract overly long segments
+ if (m_body[2].flActualLength > m_idealSegmentLength * 2.0)
+ return false;
+
+ if (!IsValidConnection( 1, 3 ))
+ return false;
+
+ m_body.Remove( 2 );
+
+ CalcRelaxedLength( );
+
+ return true;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the first stuck node that's closest to the head
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+int CNPC_Hydra::VirtualRoot( )
+{
+ // first first stuck segment closest to head;
+ int iStuckHead;
+ for (iStuckHead = m_body.Count() - 2; iStuckHead > 1; iStuckHead--)
+ {
+ if (m_body[iStuckHead].bStuck)
+ {
+ return iStuckHead;
+ }
+ }
+
+ return 1;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Insert a node before the given node.
+// Input :
+// Output :
+//-----------------------------------------------------------------------------
+
+bool CNPC_Hydra::AddNodeBefore( int iNode )
+{
+ if (iNode < 1)
+ return false;
+
+ HydraBone bone;
+
+ bone.vecPos = (m_body[iNode].vecPos + m_body[iNode-1].vecPos) * 0.5;
+ bone.vecDelta = (m_body[iNode].vecDelta + m_body[iNode-1].vecDelta) * 0.5;
+
+ /*
+ // FIXME: can't do this, may be embedded in the world
+ int i0 = (iNode>2)?iNode-2:0;
+ int i1 = (iNode>1)?iNode-1:0;
+ int i2 = iNode;
+ int i3 = (iNode<m_body.Count()-1)?iNode+1:m_body.Count()-1;
+ Catmull_Rom_Spline( m_body[i0].vecPos, m_body[i1].vecPos, m_body[i2].vecPos, m_body[i3].vecPos, 0.5, bone.vecPos );
+ */
+
+ bone.flActualLength = (m_body[iNode].vecPos - bone.vecPos).Length();
+ bone.flIdealLength = m_idealSegmentLength;
+
+ m_body[iNode-1].flActualLength = bone.flActualLength;
+
+ //Vector vecGoalPos;
+ //float flGoalInfluence;
+
+
+ m_body.InsertBefore( iNode, bone );
+
+ return true;
+}
+
+
+bool CNPC_Hydra::AddNodeAfter( int iNode )
+{
+ AddNodeBefore( iNode + 1 );
+ return false;
+}
+
+
+bool CNPC_Hydra::GrowFromVirtualRoot( )
+{
+ if (m_body[1].flActualLength < m_idealSegmentLength * 0.5)
+ return false;
+
+ return AddNodeAfter( 1 );
+}
+
+
+bool CNPC_Hydra::GrowFromMostStretched( )
+{
+ int iNode = VirtualRoot( );
+
+ int iLongest = iNode;
+ float dist = m_idealSegmentLength * 0.5;
+
+ for (iNode; iNode < m_body.Count() - 1; iNode++)
+ {
+ if (m_body[iNode].flActualLength > dist)
+ {
+ iLongest = iNode;
+ dist = m_body[iNode].flActualLength;
+ }
+ }
+
+ if (m_body[iLongest].flActualLength <= dist)
+ {
+ return AddNodeAfter( iLongest );
+ }
+ return false;
+}
+
+
+void CNPC_Hydra::CalcRelaxedLength( )
+{
+ m_flRelaxedLength = m_idealSegmentLength * (m_body.Count() -2) + HYDRA_OUTWARD_BIAS;
+}
+
+
+bool CNPC_Hydra::IsValidConnection( int iNode0, int iNode1 )
+{
+ trace_t tr;
+ // check to make sure new connection is valid
+ AI_TraceHull(m_body[iNode0].vecPos, m_body[iNode1].vecPos,
+ Vector( -2, -2, -2 ), Vector( 2, 2, 2 ),
+ MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr);
+
+ if (tr.fraction == 1.0)
+ {
+ return true;
+ }
+ return false;
+}
+
+
+//-------------------------------------
+
+float CNPC_Hydra::MaxYawSpeed()
+{
+ return 0;
+
+ if( IsMoving() )
+ {
+ return 20;
+ }
+
+ switch( GetActivity() )
+ {
+ case ACT_180_LEFT:
+ return 30;
+ break;
+
+ case ACT_TURN_LEFT:
+ case ACT_TURN_RIGHT:
+ return 30;
+ break;
+ default:
+ return 15;
+ break;
+ }
+}
+
+//-------------------------------------
+
+int CNPC_Hydra::TranslateSchedule( int scheduleType )
+{
+ return BaseClass::TranslateSchedule( scheduleType );
+}
+
+//-------------------------------------
+
+void CNPC_Hydra::HandleAnimEvent( animevent_t *pEvent )
+{
+ BaseClass::HandleAnimEvent( pEvent );
+}
+
+//-------------------------------------
+
+void CNPC_Hydra::PrescheduleThink()
+{
+ BaseClass::PrescheduleThink();
+ if ( m_bStabbedEntity )
+ {
+ UpdateStabbedEntity();
+ }
+}
+
+//-------------------------------------
+
+int CNPC_Hydra::SelectSchedule ()
+{
+ switch ( m_NPCState )
+ {
+ case NPC_STATE_IDLE:
+ {
+ SetState( NPC_STATE_ALERT );
+ return SCHED_HYDRA_DEPLOY;
+ }
+ break;
+
+ case NPC_STATE_ALERT:
+ {
+ return SCHED_HYDRA_STAB;
+ }
+ break;
+
+ case NPC_STATE_COMBAT:
+ {
+ if (HasCondition( COND_HYDRA_SNAGGED ))
+ {
+ return SCHED_HYDRA_PULLBACK;
+ }
+ else if (HasCondition( COND_HYDRA_OVERSTRETCH ))
+ {
+ return SCHED_HYDRA_STAB;
+ }
+ return SCHED_HYDRA_STAB;
+ }
+ break;
+ }
+
+ return BaseClass::SelectSchedule();
+}
+
+//-------------------------------------
+
+void CNPC_Hydra::StartTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_HYDRA_DEPLOY:
+ m_vecHeadGoal = GetAbsOrigin( ) + m_vecOutward * 100;
+ m_idealLength = 100;
+ m_vecHeadDir = m_vecOutward;
+ return;
+ case TASK_HYDRA_PREP_STAB:
+ {
+ m_flTaskEndTime = gpGlobals->curtime + pTask->flTaskData;
+
+ // Go outward
+ m_vecHeadGoal = GetAbsOrigin( ) + m_vecOutward * 100;
+ SetTarget( (CBaseEntity *)UTIL_GetLocalPlayer() );
+ if (GetEnemy())
+ {
+ SetTarget( GetEnemy() );
+ }
+
+ //CPASAttenuationFilter filter( this, "NPC_Hydra.Alert" );
+ //Vector vecHead = EyePosition();
+ //EmitSound( filter, entindex(), "NPC_Hydra.Alert", &vecHead );
+ }
+ return;
+
+ case TASK_HYDRA_STAB:
+ {
+ //CPASAttenuationFilter filter( this, "NPC_Hydra.Attack" );
+ //Vector vecHead = EyePosition();
+ //EmitSound( filter, entindex(), "NPC_Hydra.Attack", &vecHead );
+
+ m_flTaskEndTime = gpGlobals->curtime + 0.5;
+ }
+ return;
+
+ case TASK_HYDRA_PULLBACK:
+ m_vecHeadGoal = GetAbsOrigin( ) + m_vecOutward * pTask->flTaskData;
+ m_idealLength = pTask->flTaskData * 1.1;
+ return;
+
+ default:
+ BaseClass::StartTask( pTask );
+ break;
+ }
+
+}
+
+//-------------------------------------
+
+void CNPC_Hydra::RunTask( const Task_t *pTask )
+{
+ switch( pTask->iTask )
+ {
+ case TASK_HYDRA_DEPLOY:
+ {
+ m_flHeadGoalInfluence = 1.0;
+ float dist = (EyePosition() - m_vecHeadGoal).Length();
+
+ if (dist < m_idealSegmentLength)
+ {
+ TaskComplete();
+ }
+
+ AimHeadInTravelDirection( 0.2 );
+ }
+ break;
+
+ case TASK_HYDRA_PREP_STAB:
+ {
+ int i;
+
+ if (m_body.Count() < 2)
+ {
+ TaskFail( "hydra is too short to begin stab" );
+ return;
+ }
+
+ CBaseEntity *pTarget = GetTarget();
+ if (pTarget == NULL)
+ {
+ TaskFail( FAIL_NO_TARGET );
+ }
+
+ if (pTarget->IsPlayer())
+ {
+ m_vecTarget = pTarget->EyePosition( );
+ }
+ else
+ {
+ m_vecTarget = pTarget->BodyTarget( EyePosition( ) );
+ }
+
+ float distToTarget = (m_vecTarget - m_vecHeadGoal).Length();
+ float distToBase = (m_vecHeadGoal - GetAbsOrigin()).Length();
+ m_idealLength = distToTarget + distToBase * 0.5;
+
+ if (m_idealLength > HYDRA_MAX_LENGTH)
+ m_idealLength = HYDRA_MAX_LENGTH;
+
+ if (distToTarget < 100.0)
+ {
+ m_vecTargetDir = (m_vecTarget - m_vecHeadGoal);
+ VectorNormalize( m_vecTargetDir );
+ m_vecHeadGoal = m_vecHeadGoal - m_vecTargetDir * (100 - distToTarget) * 0.5;
+ }
+ else if (distToTarget > 200.0)
+ {
+ m_vecTargetDir = (m_vecTarget - m_vecHeadGoal);
+ VectorNormalize( m_vecTargetDir );
+ m_vecHeadGoal = m_vecHeadGoal - m_vecTargetDir * (200.0 - distToTarget) * 0.5;
+ }
+
+ // face enemy
+ m_vecTargetDir = (m_vecTarget - m_body[m_body.Count()-1].vecPos);
+ VectorNormalize( m_vecTargetDir );
+ m_vecHeadDir = m_vecHeadDir * 0.6 + m_vecTargetDir * 0.4;
+ VectorNormalize( m_vecHeadDir.GetForModify() );
+
+ // build tension towards strike time
+ float influence = 1.0 - (m_flTaskEndTime - gpGlobals->curtime) / pTask->flTaskData;
+ if (influence > 1)
+ influence = 1.0;
+
+ influence = influence * influence * influence;
+
+ m_flHeadGoalInfluence = influence;
+
+ // keep head segment straight
+ i = m_body.Count() - 2;
+ m_body[i].vecGoalPos = m_vecHeadGoal - m_vecHeadDir * m_body[i].flActualLength;
+ m_body[i].flGoalInfluence = influence;
+
+ // curve neck into spiral
+ float distBackFromHead = m_body[i].flActualLength;
+ Vector right, up;
+ VectorVectors( m_vecHeadDir, right, up );
+
+ for (i = i - 1; i > 1 && distBackFromHead < distToTarget; i--)
+ {
+ distBackFromHead += m_body[i].flActualLength;
+
+ float r = (distBackFromHead / 200) * 3.1415 * 2;
+
+ // spiral
+ Vector p0 = m_vecHeadGoal
+ - m_vecHeadDir * distBackFromHead * 0.5
+ + cos( r ) * m_body[i].flActualLength * right
+ + sin( r ) * m_body[i].flActualLength * up;
+
+ // base
+ r = (distBackFromHead / m_idealLength) * 3.1415 * 0.2;
+ r = sin( r );
+ p0 = p0 * (1 - r) + r * GetAbsOrigin();
+
+ m_body[i].vecGoalPos = p0;
+
+ m_body[i].flGoalInfluence = influence * (1.0 - (distBackFromHead / distToTarget));
+
+ /*
+ if ( (pEnemy->EyePosition( ) - m_body[i].vecPos).Length() < distBackFromHead)
+ {
+ if ( gpGlobals->curtime - m_flLastAttackTime > 4.0)
+ {
+ TaskComplete();
+ }
+ return;
+ }
+ */
+ }
+
+ // look to see if any of the goal positions are stuck
+ for (i = i; i < m_body.Count() - 1; i++)
+ {
+ if (m_body[i].bStuck)
+ {
+ Vector delta = DotProduct( m_body[i].vecGoalPos - m_body[i].vecPos, m_vecHeadDir) * m_vecHeadDir;
+ m_vecHeadGoal -= delta * m_body[i].flGoalInfluence;
+ break;
+ }
+ }
+
+ if ( gpGlobals->curtime >= m_flTaskEndTime )
+ {
+ if (distToTarget < 500)
+ {
+ TaskComplete( );
+ return;
+ }
+ else
+ {
+ TaskFail( "target is too far away" );
+ return;
+ }
+ }
+ }
+ return;
+
+ case TASK_HYDRA_STAB:
+ {
+ int i;
+
+ if (m_body.Count() < 2)
+ {
+ TaskFail( "hydra is too short to begin stab" );
+ return;
+ }
+
+ if (m_flTaskEndTime <= gpGlobals->curtime)
+ {
+ TaskComplete( );
+ return;
+ }
+
+ m_flHeadGoalInfluence = 1.0;
+
+ // face enemy
+ //m_vecHeadDir = (pEnemy->EyePosition( ) - m_body[m_body.Count()-1].vecPos);
+ //VectorNormalize( m_vecHeadDir.GetForModify() );
+
+ // keep head segment straight
+ i = m_body.Count() - 2;
+ m_body[i].vecGoalPos = m_vecHeadGoal + m_vecHeadDir * m_body[i].flActualLength;
+ m_body[i].flGoalInfluence = 1.0;
+
+ Vector vecToTarget = (m_vecTarget - EyePosition( ));
+
+ // check to see if we went past target
+ if (DotProduct( vecToTarget, m_vecHeadDir ) < 0.0)
+ {
+ TaskComplete( );
+ return;
+ }
+
+ float distToTarget = vecToTarget.Length();
+ float distToBase = (EyePosition( ) - GetAbsOrigin()).Length();
+ m_idealLength = distToTarget + distToBase;
+
+ /*
+ if (distToTarget < 20)
+ {
+ m_vecHeadGoal = m_vecTarget;
+ SetLastAttackTime( gpGlobals->curtime );
+ TaskComplete();
+ return;
+ }
+ else
+ */
+ {
+ // hit enemy
+ m_vecHeadGoal = m_vecTarget + m_vecHeadDir * 300;
+ }
+
+ if (m_idealLength > HYDRA_MAX_LENGTH)
+ m_idealLength = HYDRA_MAX_LENGTH;
+
+ // curve neck into spiral
+ float distBackFromHead = m_body[i].flActualLength;
+ Vector right, up;
+ VectorVectors( m_vecHeadDir, right, up );
+
+#if 1
+ for (i = i - 1; i > 1 && distBackFromHead < distToTarget; i--)
+ {
+ Vector p0 = m_vecHeadGoal - m_vecHeadDir * distBackFromHead * 1.0;
+
+ m_body[i].vecGoalPos = p0;
+
+ if ((m_vecTarget - m_body[i].vecPos).Length() > distToTarget + distBackFromHead)
+ {
+ m_body[i].flGoalInfluence = 1.0 - (distBackFromHead / distToTarget);
+ }
+ else
+ {
+ m_body[i].vecGoalPos = EyePosition( ) - m_vecHeadDir * distBackFromHead;
+ m_body[i].flGoalInfluence = 1.0 - (distBackFromHead / distToTarget);
+ }
+
+ distBackFromHead += m_body[i].flActualLength;
+ }
+#endif
+ }
+ return;
+
+ case TASK_HYDRA_PULLBACK:
+ {
+ if (m_body.Count() < 2)
+ {
+ TaskFail( "hydra is too short to begin stab" );
+ return;
+ }
+ CBaseEntity *pEnemy = (CBaseEntity *)UTIL_GetLocalPlayer();
+ if (GetEnemy() != NULL)
+ {
+ pEnemy = GetEnemy();
+ }
+
+ AimHeadInTravelDirection( 0.2 );
+
+ // float dist = (EyePosition() - m_vecHeadGoal).Length();
+
+ if (m_flCurrentLength < m_idealLength + m_idealSegmentLength)
+ {
+ TaskComplete();
+ }
+ }
+ break;
+
+ default:
+ BaseClass::RunTask( pTask );
+ break;
+ }
+
+}
+
+//-------------------------------------
+
+Vector CNPC_Hydra::EyePosition( )
+{
+ int i = m_body.Count() - 1;
+
+ if (i >= 0)
+ {
+ return m_body[i].vecPos;
+ }
+ return GetAbsOrigin();
+}
+
+const QAngle &CNPC_Hydra::EyeAngles()
+{
+ return GetAbsAngles();
+}
+
+
+Vector CNPC_Hydra::BodyTarget( const Vector &posSrc, bool bNoisy)
+{
+ int i;
+
+ if (m_body.Count() < 2)
+ {
+ return GetAbsOrigin();
+ }
+
+ int iShortest = 1;
+ float flShortestDist = (posSrc - m_body[iShortest].vecPos).LengthSqr();
+ for (i = 2; i < m_body.Count(); i++)
+ {
+ float flDist = (posSrc - m_body[i].vecPos).LengthSqr();
+ if (flDist < flShortestDist)
+ {
+ iShortest = i;
+ flShortestDist = flDist;
+ }
+ }
+
+ // NDebugOverlay::Box(m_body[iShortest].vecPos, Vector( -2, -2, -2 ), Vector( 2, 2, 2 ), 0, 0, 255, 20, .1);
+
+ return m_body[iShortest].vecPos;
+}
+
+
+void CNPC_Hydra::AimHeadInTravelDirection( float flInfluence )
+{
+
+ // aim in the direction of movement enemy
+ Vector delta = m_body[m_body.Count()-1].vecDelta;
+ VectorNormalize( delta );
+ if (DotProduct( delta, m_vecHeadDir ) < 0)
+ {
+ delta = -delta;
+ }
+
+ m_vecHeadDir = m_vecHeadDir * (1 - flInfluence) + delta * flInfluence;
+ VectorNormalize( m_vecHeadDir.GetForModify() );
+}
+
+//-------------------------------------
+
+//-----------------------------------------------------------------------------
+// Purpose: Hydra impaling is done by creating an entity, forming a constraint
+// between that entity and the target ragdoll, and then updating then
+// entity to follow the hydra.
+//-----------------------------------------------------------------------------
+
+//-----------------------------------------------------------------------------
+// Purpose: This is the entity we create to follow the hydra
+//-----------------------------------------------------------------------------
+class CHydraImpale : public CBaseAnimating
+{
+ DECLARE_CLASS( CHydraImpale, CBaseAnimating );
+public:
+ DECLARE_DATADESC();
+
+ void Spawn( void );
+ void Precache( void );
+ void ImpaleThink( void );
+
+ IPhysicsConstraint *CreateConstraint( CNPC_Hydra *pHydra, IPhysicsObject *pTargetPhys, IPhysicsConstraintGroup *pGroup );
+ static CHydraImpale *Create( CNPC_Hydra *pHydra, CBaseEntity *pObject2 );
+
+public:
+ IPhysicsConstraint *m_pConstraint;
+ CHandle<CNPC_Hydra> m_hHydra;
+};
+
+BEGIN_DATADESC( CHydraImpale )
+ DEFINE_PHYSPTR( m_pConstraint ),
+ DEFINE_FIELD( m_hHydra, FIELD_EHANDLE ),
+
+ DEFINE_THINKFUNC( ImpaleThink ),
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS( hydra_impale, CHydraImpale );
+
+//-----------------------------------------------------------------------------
+// Purpose: To by usable by the constraint system, this needs to have a phys model.
+//-----------------------------------------------------------------------------
+void CHydraImpale::Spawn( void )
+{
+ Precache();
+ SetModel( "models/props_junk/cardboard_box001a.mdl" );
+ AddEffects( EF_NODRAW );
+
+ // We don't want this to be solid, because we don't want it to collide with the hydra.
+ SetSolid( SOLID_VPHYSICS );
+ AddSolidFlags( FSOLID_NOT_SOLID );
+ VPhysicsInitShadow( false, false );
+
+ // Disable movement on this sucker, we're going to move him manually
+ SetMoveType( MOVETYPE_FLY );
+
+ BaseClass::Spawn();
+
+ m_pConstraint = NULL;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CHydraImpale::Precache( void )
+{
+ PrecacheModel( "models/props_junk/cardboard_box001a.mdl" );
+ BaseClass::Precache();
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update the impale entity's position to the hydra's desired
+//-----------------------------------------------------------------------------
+void CHydraImpale::ImpaleThink( void )
+{
+ if ( !m_hHydra )
+ {
+ // Remove ourselves.
+ m_pConstraint->Deactivate();
+ UTIL_Remove( this );
+ return;
+ }
+
+ // Ask the Hydra where he'd like the ragdoll, and move ourselves there
+ Vector vecOrigin;
+ QAngle vecAngles;
+ m_hHydra->GetDesiredImpaledPosition( &vecOrigin, &vecAngles );
+ SetAbsOrigin( vecOrigin );
+ SetAbsAngles( vecAngles );
+
+ //NDebugOverlay::Cross3D( vecOrigin, Vector( -5, -5, -5 ), Vector( 5, 5, 5 ), 255, 0, 0, 20, .1);
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Activate/create the constraint
+//-----------------------------------------------------------------------------
+IPhysicsConstraint *CHydraImpale::CreateConstraint( CNPC_Hydra *pHydra, IPhysicsObject *pTargetPhys, IPhysicsConstraintGroup *pGroup )
+{
+ m_hHydra = pHydra;
+
+ IPhysicsObject *pImpalePhysObject = VPhysicsGetObject();
+ Assert( pImpalePhysObject );
+
+ constraint_fixedparams_t fixed;
+ fixed.Defaults();
+ fixed.InitWithCurrentObjectState( pImpalePhysObject, pTargetPhys );
+ fixed.constraint.Defaults();
+
+ m_pConstraint = physenv->CreateFixedConstraint( pImpalePhysObject, pTargetPhys, pGroup, fixed );
+ if ( m_pConstraint )
+ {
+ m_pConstraint->SetGameData( (void *)this );
+ }
+
+ SetThink( ImpaleThink );
+ SetNextThink( gpGlobals->curtime );
+ return m_pConstraint;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Create a Hydra Impale between the hydra and the entity passed in
+//-----------------------------------------------------------------------------
+CHydraImpale *CHydraImpale::Create( CNPC_Hydra *pHydra, CBaseEntity *pTarget )
+{
+ Vector vecOrigin;
+ QAngle vecAngles;
+ pHydra->GetDesiredImpaledPosition( &vecOrigin, &vecAngles );
+ pTarget->Teleport( &vecOrigin, &vecAngles, &vec3_origin );
+
+ CHydraImpale *pImpale = (CHydraImpale *)CBaseEntity::Create( "hydra_impale", vecOrigin, vecAngles );
+ if ( !pImpale )
+ return NULL;
+
+ IPhysicsObject *pTargetPhysObject = pTarget->VPhysicsGetObject();
+ if ( !pTargetPhysObject )
+ {
+ DevMsg(" Error: Tried to hydra_impale an entity without a physics model.\n" );
+ return NULL;
+ }
+
+ IPhysicsConstraintGroup *pGroup = NULL;
+ // Ragdoll? If so, use it's constraint group
+ CRagdollProp *pRagdoll = dynamic_cast<CRagdollProp*>(pTarget);
+ if ( pRagdoll )
+ {
+ pGroup = pRagdoll->GetRagdoll()->pGroup;
+ }
+
+ if ( !pImpale->CreateConstraint( pHydra, pTargetPhysObject, pGroup ) )
+ return NULL;
+
+ return pImpale;
+}
+
+void CNPC_Hydra::AttachStabbedEntity( CBaseAnimating *pAnimating, Vector vecForce, trace_t &tr )
+{
+ CTakeDamageInfo info( this, this, pAnimating->m_iHealth+25, DMG_SLASH );
+ info.SetDamageForce( vecForce );
+ info.SetDamagePosition( tr.endpos );
+
+ CBaseEntity *pRagdoll = CreateServerRagdoll( pAnimating, 0, info, COLLISION_GROUP_INTERACTIVE_DEBRIS );
+
+ // Create our impale entity
+ CHydraImpale::Create( this, pRagdoll );
+
+ m_bStabbedEntity = true;
+
+ UTIL_Remove( pAnimating );
+}
+
+void CNPC_Hydra::UpdateStabbedEntity( void )
+{
+ /*
+ CBaseEntity *pEntity = m_grabController.GetAttached();
+ if ( !pEntity )
+ {
+ DetachStabbedEntity( false );
+ return;
+ }
+
+ QAngle vecAngles(0,0,1);
+ Vector vecOrigin = m_body[m_body.Count()-2].vecPos;
+
+ //NDebugOverlay::Cross3D( vecOrigin, Vector( -5, -5, -5 ), Vector( 5, 5, 5 ), 255, 0, 0, 20, .1);
+
+ m_grabController.SetTargetPosition( vecOrigin, vecAngles );
+ */
+}
+
+void CNPC_Hydra::DetachStabbedEntity( bool playSound )
+{
+ /*
+ CBaseEntity *pObject = m_grabController.GetAttached();
+ if ( pObject != NULL )
+ {
+ IPhysicsObject *pPhysics = pObject->VPhysicsGetObject();
+
+ // Enable collision with this object again
+ if ( pPhysics != NULL )
+ {
+ physenv->EnableCollisions( pPhysics, VPhysicsGetObject() );
+ pPhysics->RecheckCollisionFilter();
+ }
+ }
+
+ m_grabController.DetachEntity();
+ */
+
+ if ( playSound )
+ {
+ //Play the detach sound
+ }
+
+ m_bStabbedEntity = false;
+}
+
+void CNPC_Hydra::GetDesiredImpaledPosition( Vector *vecOrigin, QAngle *vecAngles )
+{
+ *vecOrigin = m_body[m_body.Count()-2].vecPos;
+ *vecAngles = QAngle(0,0,0);
+}
+
+//-----------------------------------------------------------------------------
+//
+// CNPC_Hydra Schedules
+//
+//-------------------------------------
+
+
+AI_BEGIN_CUSTOM_NPC( npc_hydra, CNPC_Hydra )
+
+ //Register our interactions
+
+ //Conditions
+ DECLARE_CONDITION( COND_HYDRA_SNAGGED )
+ DECLARE_CONDITION( COND_HYDRA_STUCK )
+ DECLARE_CONDITION( COND_HYDRA_OVERSHOOT )
+ DECLARE_CONDITION( COND_HYDRA_OVERSTRETCH )
+ DECLARE_CONDITION( COND_HYDRA_STRIKE )
+ DECLARE_CONDITION( COND_HYDRA_NOSTUCK )
+
+ //Squad slots
+
+ //Tasks
+ DECLARE_TASK( TASK_HYDRA_RETRACT )
+ DECLARE_TASK( TASK_HYDRA_DEPLOY )
+ DECLARE_TASK( TASK_HYDRA_GET_OBJECT )
+ DECLARE_TASK( TASK_HYDRA_THROW_OBJECT )
+ DECLARE_TASK( TASK_HYDRA_PREP_STAB )
+ DECLARE_TASK( TASK_HYDRA_STAB )
+ DECLARE_TASK( TASK_HYDRA_PULLBACK )
+
+ //Activities
+ DECLARE_ACTIVITY( ACT_HYDRA_COWER )
+ DECLARE_ACTIVITY( ACT_HYDRA_STAB )
+
+ //=========================================================
+ // > SCHED_HYDRA_STAND_LOOK
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HYDRA_DEPLOY,
+
+ " Tasks"
+ " TASK_HYDRA_DEPLOY 0"
+ " TASK_WAIT 0.5"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ )
+
+ //=========================================================
+ // > SCHED_HYDRA_COWER
+ //=========================================================
+ DEFINE_SCHEDULE
+ (
+ SCHED_HYDRA_RETRACT,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_SET_ACTIVITY ACTIVITY:ACT_HYDRA_COWER"
+ " TASK_WAIT 0.5"
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HYDRA_IDLE,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WAIT_INDEFINITE 0"
+ ""
+ " Interrupts "
+ " COND_NEW_ENEMY"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HYDRA_STAB,
+
+ " Tasks"
+ " TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HYDRA_DEPLOY"
+ " TASK_HYDRA_PREP_STAB 4.0"
+ " TASK_HYDRA_STAB 0"
+ " TASK_WAIT 0.5"
+ // " TASK_HYDRA_PULLBACK 100"
+ ""
+ " Interrupts "
+ " COND_NEW_ENEMY"
+ " COND_HYDRA_OVERSTRETCH"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HYDRA_PULLBACK,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_WAIT 0.4"
+ " TASK_HYDRA_PULLBACK 100"
+ ""
+ " Interrupts "
+ " COND_NEW_ENEMY"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HYDRA_THROW,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_HYDRA_GET_OBJECT 0"
+ " TASK_WAIT_FOR_MOVEMENT 0"
+ " TASK_HYDRA_THROW_OBJECT 0"
+ " TASK_WAIT 1"
+ ""
+ " Interrupts"
+ )
+
+ DEFINE_SCHEDULE
+ (
+ SCHED_HYDRA_RANGE_ATTACK,
+
+ " Tasks"
+ " TASK_STOP_MOVING 0"
+ " TASK_ANNOUNCE_ATTACK 1" // 1 = primary attack
+ " TASK_FACE_ENEMY 0"
+ " TASK_RANGE_ATTACK1 0"
+ ""
+ " Interrupts"
+ " COND_NEW_ENEMY"
+ " COND_ENEMY_DEAD"
+ " COND_LIGHT_DAMAGE"
+ " COND_HEAVY_DAMAGE"
+ " COND_ENEMY_OCCLUDED"
+ " COND_NO_PRIMARY_AMMO"
+ " COND_HEAR_DANGER"
+ )
+
+AI_END_CUSTOM_NPC()
+
+