You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1952 lines
49 KiB
1952 lines
49 KiB
//========= 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() |
|
|
|
|
|
|