Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.
 
 
 
 
 
 

1558 lines
42 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Ichthyosaur - buh bum... buh bum...
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "ai_basenpc.h"
#include "ai_task.h"
#include "ai_default.h"
#include "ai_schedule.h"
#include "ai_hull.h"
#include "ai_interactions.h"
#include "ai_navigator.h"
#include "ai_motor.h"
#include "activitylist.h"
#include "game.h"
#include "npcevent.h"
#include "player.h"
#include "entitylist.h"
#include "soundenvelope.h"
#include "shake.h"
#include "ndebugoverlay.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "movevars_shared.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar sk_ichthyosaur_health( "sk_ichthyosaur_health", "0" );
ConVar sk_ichthyosaur_melee_dmg( "sk_ichthyosaur_melee_dmg", "0" );
#define ICHTHYOSAUR_MODEL "models/ichthyosaur.mdl"
#define ICH_HEIGHT_PREFERENCE 16.0f
#define ICH_DEPTH_PREFERENCE 8.0f
#define ICH_WAYPOINT_DISTANCE 64.0f
#define ICH_AE_BITE 11
#define ICH_AE_BITE_START 12
#define ICH_SWIM_SPEED_WALK 150
#define ICH_SWIM_SPEED_RUN 500
#define ICH_MIN_TURN_SPEED 4.0f
#define ICH_MAX_TURN_SPEED 30.0f
#define ENVELOPE_CONTROLLER (CSoundEnvelopeController::GetController())
#define FEELER_COLLISION 0
#define FEELER_COLLISION_VISUALIZE (FEELER_COLLISION&&0)
enum IchthyosaurMoveType_t
{
ICH_MOVETYPE_SEEK = 0, // Fly through the target without stopping.
ICH_MOVETYPE_ARRIVE // Slow down and stop at target.
};
//
// CNPC_Ichthyosaur
//
class CNPC_Ichthyosaur : public CAI_BaseNPC
{
public:
DECLARE_CLASS( CNPC_Ichthyosaur, CAI_BaseNPC );
DECLARE_DATADESC();
CNPC_Ichthyosaur( void ) {}
int SelectSchedule( void );
int MeleeAttack1Conditions( float flDot, float flDist );
int OnTakeDamage_Alive( const CTakeDamageInfo &info );
int TranslateSchedule( int type );
void Precache( void );
void Spawn( void );
void MoveFlyExecute( CBaseEntity *pTargetEnt, const Vector & vecDir, float flDistance, float flInterval );
void HandleAnimEvent( animevent_t *pEvent );
void PrescheduleThink( void );
bool OverrideMove( float flInterval );
void StartTask( const Task_t *pTask );
void RunTask( const Task_t *pTask );
void TranslateNavGoal( CBaseEntity *pEnemy, Vector &chasePosition );
float GetDefaultNavGoalTolerance();
float MaxYawSpeed( void );
Class_T Classify( void ) { return CLASS_ANTLION; } //FIXME: No classification for various wildlife?
bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL );
private:
bool SteerAvoidObstacles( Vector &Steer, const Vector &Velocity, const Vector &Forward, const Vector &Right, const Vector &Up );
bool Beached( void );
void DoMovement( float flInterval, const Vector &MoveTarget, int eMoveType );
void SteerArrive( Vector &Steer, const Vector &Target );
void SteerSeek( Vector &Steer, const Vector &Target );
void ClampSteer( Vector &SteerAbs, Vector &SteerRel, Vector &forward, Vector &right, Vector &up );
void AddSwimNoise( Vector *velocity );
void Bite( void );
void EnsnareVictim( CBaseEntity *pVictim );
void ReleaseVictim( void );
void DragVictim( float moveDist );
void SetPoses( Vector moveRel, float speed );
//void IchTouch( CBaseEntity *pOther );
float GetGroundSpeed( void );
#if FEELER_COLLISION
Vector DoProbe( const Vector &Probe );
Vector m_LastSteer;
#endif
CBaseEntity *m_pVictim;
static const Vector m_vecAccelerationMax;
static const Vector m_vecAccelerationMin;
Vector m_vecLastMoveTarget;
float m_flNextBiteTime;
float m_flHoldTime;
float m_flSwimSpeed;
float m_flTailYaw;
float m_flTailPitch;
float m_flNextPingTime;
float m_flNextGrowlTime;
bool m_bHasMoveTarget;
bool m_bIgnoreSurface;
//CSoundPatch *m_pSwimSound;
//CSoundPatch *m_pVoiceSound;
DEFINE_CUSTOM_AI;
};
//Acceleration definitions
const Vector CNPC_Ichthyosaur::m_vecAccelerationMax = Vector( 256, 1024, 512 );
const Vector CNPC_Ichthyosaur::m_vecAccelerationMin = Vector( -256, -1024, -512 );
//Data description
BEGIN_DATADESC( CNPC_Ichthyosaur )
// Silence classcheck
// DEFINE_FIELD( m_LastSteer, FIELD_VECTOR ),
DEFINE_FIELD( m_pVictim, FIELD_CLASSPTR ),
DEFINE_FIELD( m_vecLastMoveTarget, FIELD_VECTOR ),
DEFINE_FIELD( m_flNextBiteTime, FIELD_FLOAT ),
DEFINE_FIELD( m_flHoldTime, FIELD_FLOAT ),
DEFINE_FIELD( m_flSwimSpeed, FIELD_FLOAT ),
DEFINE_FIELD( m_flTailYaw, FIELD_FLOAT ),
DEFINE_FIELD( m_flTailPitch, FIELD_FLOAT ),
DEFINE_FIELD( m_flNextPingTime, FIELD_FLOAT ),
DEFINE_FIELD( m_flNextGrowlTime, FIELD_FLOAT ),
DEFINE_FIELD( m_bHasMoveTarget, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bIgnoreSurface, FIELD_BOOLEAN ),
//DEFINE_FUNCTION( IchTouch ),
END_DATADESC()
//Schedules
enum IchSchedules
{
SCHED_ICH_CHASE_ENEMY = LAST_SHARED_SCHEDULE,
SCHED_ICH_PATROL_RUN,
SCHED_ICH_PATROL_WALK,
SCHED_ICH_DROWN_VICTIM,
SCHED_ICH_MELEE_ATTACK1,
SCHED_ICH_THRASH,
};
//Tasks
enum IchTasks
{
TASK_ICH_GET_PATH_TO_RANDOM_NODE = LAST_SHARED_TASK,
TASK_ICH_GET_PATH_TO_DROWN_NODE,
TASK_ICH_THRASH_PATH,
};
//Activities
int ACT_ICH_THRASH;
int ACT_ICH_BITE_HIT;
int ACT_ICH_BITE_MISS;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::InitCustomSchedules( void )
{
INIT_CUSTOM_AI( CNPC_Ichthyosaur );
//Interaction REGISTER_INTERACTION( g_interactionAntlionAttacked );
//Schedules
ADD_CUSTOM_SCHEDULE( CNPC_Ichthyosaur, SCHED_ICH_CHASE_ENEMY );
ADD_CUSTOM_SCHEDULE( CNPC_Ichthyosaur, SCHED_ICH_PATROL_RUN );
ADD_CUSTOM_SCHEDULE( CNPC_Ichthyosaur, SCHED_ICH_PATROL_WALK );
ADD_CUSTOM_SCHEDULE( CNPC_Ichthyosaur, SCHED_ICH_DROWN_VICTIM );
ADD_CUSTOM_SCHEDULE( CNPC_Ichthyosaur, SCHED_ICH_MELEE_ATTACK1 );
ADD_CUSTOM_SCHEDULE( CNPC_Ichthyosaur, SCHED_ICH_THRASH );
//Tasks
ADD_CUSTOM_TASK( CNPC_Ichthyosaur, TASK_ICH_GET_PATH_TO_RANDOM_NODE );
ADD_CUSTOM_TASK( CNPC_Ichthyosaur, TASK_ICH_GET_PATH_TO_DROWN_NODE );
ADD_CUSTOM_TASK( CNPC_Ichthyosaur, TASK_ICH_THRASH_PATH );
//Conditions ADD_CUSTOM_CONDITION( CNPC_CombineGuard, COND_ANTLIONGRUB_HEARD_SQUEAL );
//Activities
ADD_CUSTOM_ACTIVITY( CNPC_Ichthyosaur, ACT_ICH_THRASH );
ADD_CUSTOM_ACTIVITY( CNPC_Ichthyosaur, ACT_ICH_BITE_HIT );
ADD_CUSTOM_ACTIVITY( CNPC_Ichthyosaur, ACT_ICH_BITE_MISS );
AI_LOAD_SCHEDULE( CNPC_Ichthyosaur, SCHED_ICH_CHASE_ENEMY );
AI_LOAD_SCHEDULE( CNPC_Ichthyosaur, SCHED_ICH_PATROL_RUN );
AI_LOAD_SCHEDULE( CNPC_Ichthyosaur, SCHED_ICH_PATROL_WALK );
AI_LOAD_SCHEDULE( CNPC_Ichthyosaur, SCHED_ICH_DROWN_VICTIM );
AI_LOAD_SCHEDULE( CNPC_Ichthyosaur, SCHED_ICH_MELEE_ATTACK1 );
AI_LOAD_SCHEDULE( CNPC_Ichthyosaur, SCHED_ICH_THRASH );
}
LINK_ENTITY_TO_CLASS( npc_ichthyosaur, CNPC_Ichthyosaur );
IMPLEMENT_CUSTOM_AI( npc_ichthyosaur, CNPC_Ichthyosaur );
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::Precache( void )
{
PrecacheModel( ICHTHYOSAUR_MODEL );
PrecacheScriptSound( "NPC_Ichthyosaur.Bite" );
PrecacheScriptSound( "NPC_Ichthyosaur.BiteMiss" );
PrecacheScriptSound( "NPC_Ichthyosaur.AttackGrowl" );
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::Spawn( void )
{
Precache();
SetModel( ICHTHYOSAUR_MODEL );
SetHullType(HULL_LARGE_CENTERED);
SetHullSizeNormal();
SetDefaultEyeOffset();
SetNavType( NAV_FLY );
m_NPCState = NPC_STATE_NONE;
SetBloodColor( BLOOD_COLOR_RED );
m_iHealth = sk_ichthyosaur_health.GetFloat();
m_iMaxHealth = m_iHealth;
m_flFieldOfView = -0.707; // 270 degrees
SetDistLook( 1024 );
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
SetMoveType( MOVETYPE_STEP );
AddFlag( FL_FLY | FL_STEPMOVEMENT );
m_flGroundSpeed = ICH_SWIM_SPEED_RUN;
m_bIgnoreSurface = false;
m_flSwimSpeed = 0.0f;
m_flTailYaw = 0.0f;
m_flTailPitch = 0.0f;
m_flNextBiteTime = gpGlobals->curtime;
m_flHoldTime = gpGlobals->curtime;
m_flNextPingTime = gpGlobals->curtime;
m_flNextGrowlTime = gpGlobals->curtime;
#if FEELER_COLLISION
Vector forward;
GetVectors( &forward, NULL, NULL );
m_vecCurrentVelocity = forward * m_flGroundSpeed;
#endif
//SetTouch( IchTouch );
CapabilitiesClear();
CapabilitiesAdd( bits_CAP_MOVE_FLY | bits_CAP_INNATE_MELEE_ATTACK1 );
NPCInit();
//m_pSwimSound = ENVELOPE_CONTROLLER.SoundCreate( edict(), CHAN_BODY, "xxxCONVERTTOGAMESOUNDS!!!npc/ichthyosaur/ich_amb1wav", ATTN_NORM );
//m_pVoiceSound = ENVELOPE_CONTROLLER.SoundCreate( edict(), CHAN_VOICE, "xxxCONVERTTOGAMESOUNDS!!!npc/ichthyosaur/water_breathwav", ATTN_IDLE );
//ENVELOPE_CONTROLLER.Play( m_pSwimSound, 1.0f, 100 );
//ENVELOPE_CONTROLLER.Play( m_pVoiceSound,1.0f, 100 );
BaseClass::Spawn();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pOther -
//-----------------------------------------------------------------------------
/*
void CNPC_Ichthyosaur::IchTouch( CBaseEntity *pOther )
{
}
*/
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CNPC_Ichthyosaur::SelectSchedule( void )
{
if ( m_NPCState == NPC_STATE_COMBAT )
{
if ( m_flHoldTime > gpGlobals->curtime )
return SCHED_ICH_DROWN_VICTIM;
if ( m_flNextBiteTime > gpGlobals->curtime )
return SCHED_PATROL_RUN;
if ( HasCondition( COND_CAN_MELEE_ATTACK1 ) )
return SCHED_MELEE_ATTACK1;
return SCHED_CHASE_ENEMY;
}
return BaseClass::SelectSchedule();
}
//-----------------------------------------------------------------------------
// Purpose: Handles movement towards the last move target.
// Input : flInterval -
//-----------------------------------------------------------------------------
bool CNPC_Ichthyosaur::OverrideMove( float flInterval )
{
m_flGroundSpeed = GetGroundSpeed();
if ( m_bHasMoveTarget )
{
DoMovement( flInterval, m_vecLastMoveTarget, ICH_MOVETYPE_ARRIVE );
}
else
{
DoMovement( flInterval, GetLocalOrigin(), ICH_MOVETYPE_ARRIVE );
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &probe -
// Output : Vector
//-----------------------------------------------------------------------------
#if FEELER_COLLISION
Vector CNPC_Ichthyosaur::DoProbe( const Vector &probe )
{
trace_t tr;
float fraction = 1.0f;
bool collided = false;
Vector normal = Vector( 0, 0, -1 );
float waterLevel = UTIL_WaterLevel( GetAbsOrigin(), GetAbsOrigin().z, GetAbsOrigin().z+150 );
waterLevel -= GetAbsOrigin().z;
waterLevel /= 150;
if ( waterLevel < 1.0f )
{
collided = true;
fraction = waterLevel;
}
AI_TraceHull( GetAbsOrigin(), probe, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
if ( ( collided == false ) || ( tr.fraction < fraction ) )
{
fraction = tr.fraction;
normal = tr.plane.normal;
}
if ( ( fraction < 1.0f ) && ( GetEnemy() == NULL || tr.u.ent != GetEnemy()->pev ) )
{
#if FEELER_COLLISION_VISUALIZE
NDebugOverlay::Line( GetLocalOrigin(), probe, 255, 0, 0, false, 0.1f );
#endif
Vector probeDir = probe - GetLocalOrigin();
Vector normalToProbeAndWallNormal = probeDir.Cross( normal );
Vector steeringVector = normalToProbeAndWallNormal.Cross( probeDir );
Vector velDir = GetAbsVelocity();
VectorNormalize( velDir );
float steeringForce = m_flGroundSpeed * ( 1.0f - fraction ) * normal.Dot( velDir );
if ( steeringForce < 0.0f )
{
steeringForce = -steeringForce;
}
velDir = steeringVector;
VectorNormalize( velDir );
steeringVector = steeringForce * velDir;
return steeringVector;
}
#if FEELER_COLLISION_VISUALIZE
NDebugOverlay::Line( GetLocalOrigin(), probe, 0, 255, 0, false, 0.1f );
#endif
return Vector( 0.0f, 0.0f, 0.0f );
}
#endif
//-----------------------------------------------------------------------------
// Purpose: Move the victim of a drag along with us
// Input : moveDist - our amount of travel
//-----------------------------------------------------------------------------
#define DRAG_OFFSET 50.0f
void CNPC_Ichthyosaur::DragVictim( float moveDist )
{
Vector mins, maxs;
float width;
mins = WorldAlignMins();
maxs = WorldAlignMaxs();
width = ( maxs.y - mins.y ) * 0.5f;
Vector forward, up;
GetVectors( &forward, NULL, &up );
Vector newPos = GetAbsOrigin() + ( (forward+(up*0.25f)) * ( moveDist + width + DRAG_OFFSET ) );
trace_t tr;
AI_TraceEntity( this, m_pVictim->GetAbsOrigin(), newPos, MASK_NPCSOLID, &tr );
if ( ( tr.fraction == 1.0f ) && ( tr.m_pEnt != this ) )
{
UTIL_SetOrigin( m_pVictim, tr.endpos );
}
else
{
ReleaseVictim();
}
}
//-----------------------------------------------------------------------------
// Purpose: Determines the pose parameters for the bending of the body and tail speed
// Input : moveRel - the dot products for the deviation off of each direction (f,r,u)
// speed - speed of the fish
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::SetPoses( Vector moveRel, float speed )
{
float movePerc, moveBase;
//Find out how fast we're moving in our animations boundaries
if ( GetIdealActivity() == ACT_WALK )
{
moveBase = 0.5f;
movePerc = moveBase * ( speed / ICH_SWIM_SPEED_WALK );
}
else
{
moveBase = 1.0f;
movePerc = moveBase * ( speed / ICH_SWIM_SPEED_RUN );
}
Vector tailPosition;
float flSwimSpeed = movePerc;
//Forward deviation
if ( moveRel.x > 0 )
{
flSwimSpeed *= moveBase + (( moveRel.x / m_vecAccelerationMax.x )*moveBase);
}
else if ( moveRel.x < 0 )
{
flSwimSpeed *= moveBase - (( moveRel.x / m_vecAccelerationMin.x )*moveBase);
}
//Vertical deviation
if ( moveRel.z > 0 )
{
tailPosition[PITCH] = -90.0f * ( moveRel.z / m_vecAccelerationMax.z );
}
else if ( moveRel.z < 0 )
{
tailPosition[PITCH] = 90.0f * ( moveRel.z / m_vecAccelerationMin.z );
}
else
{
tailPosition[PITCH] = 0.0f;
}
//Lateral deviation
if ( moveRel.y > 0 )
{
tailPosition[ROLL] = 25 * moveRel.y / m_vecAccelerationMax.y;
tailPosition[YAW] = -1.0f * moveRel.y / m_vecAccelerationMax.y;
}
else if ( moveRel.y < 0 )
{
tailPosition[ROLL] = -25 * moveRel.y / m_vecAccelerationMin.y;
tailPosition[YAW] = moveRel.y / m_vecAccelerationMin.y;
}
else
{
tailPosition[ROLL] = 0.0f;
tailPosition[YAW] = 0.0f;
}
//Clamp
flSwimSpeed = clamp( flSwimSpeed, 0.25f, 1.0f );
tailPosition[YAW] = clamp( tailPosition[YAW], -90.0f, 90.0f );
tailPosition[PITCH] = clamp( tailPosition[PITCH], -90.0f, 90.0f );
//Blend
m_flTailYaw = ( m_flTailYaw * 0.8f ) + ( tailPosition[YAW] * 0.2f );
m_flTailPitch = ( m_flTailPitch * 0.8f ) + ( tailPosition[PITCH] * 0.2f );
m_flSwimSpeed = ( m_flSwimSpeed * 0.8f ) + ( flSwimSpeed * 0.2f );
//Pose the body
SetPoseParameter( 0, m_flSwimSpeed );
SetPoseParameter( 1, m_flTailYaw );
SetPoseParameter( 2, m_flTailPitch );
//FIXME: Until the sequence info is reset properly after SetPoseParameter
if ( ( GetActivity() == ACT_RUN ) || ( GetActivity() == ACT_WALK ) )
{
ResetSequenceInfo();
}
//Face our current velocity
GetMotor()->SetIdealYawAndUpdate( UTIL_AngleMod( CalcIdealYaw( GetAbsOrigin() + GetAbsVelocity() ) ), AI_KEEP_YAW_SPEED );
float pitch = 0.0f;
if ( speed != 0.0f )
{
pitch = -RAD2DEG( asin( GetAbsVelocity().z / speed ) );
}
//FIXME: Framerate dependant
QAngle angles = GetLocalAngles();
angles.x = (angles.x * 0.8f) + (pitch * 0.2f);
angles.z = (angles.z * 0.9f) + (tailPosition[ROLL] * 0.1f);
SetLocalAngles( angles );
}
#define LATERAL_NOISE_MAX 2.0f
#define LATERAL_NOISE_FREQ 1.0f
#define VERTICAL_NOISE_MAX 2.0f
#define VERTICAL_NOISE_FREQ 1.0f
//-----------------------------------------------------------------------------
// Purpose:
// Input : velocity -
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::AddSwimNoise( Vector *velocity )
{
Vector right, up;
GetVectors( NULL, &right, &up );
float lNoise, vNoise;
lNoise = LATERAL_NOISE_MAX * sin( gpGlobals->curtime * LATERAL_NOISE_FREQ );
vNoise = VERTICAL_NOISE_MAX * sin( gpGlobals->curtime * VERTICAL_NOISE_FREQ );
(*velocity) += ( right * lNoise ) + ( up * vNoise );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : flInterval -
// &m_LastMoveTarget -
// eMoveType -
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::DoMovement( float flInterval, const Vector &MoveTarget, int eMoveType )
{
// dvs: something is setting this bit, causing us to stop moving and get stuck that way
Forget( bits_MEMORY_TURNING );
Vector Steer, SteerAvoid, SteerRel;
Vector forward, right, up;
//Get our orientation vectors.
GetVectors( &forward, &right, &up);
if ( ( GetActivity() == ACT_MELEE_ATTACK1 ) && ( GetEnemy() != NULL ) )
{
SteerSeek( Steer, GetEnemy()->GetAbsOrigin() );
}
else
{
//If we are approaching our goal, use an arrival steering mechanism.
if ( eMoveType == ICH_MOVETYPE_ARRIVE )
{
SteerArrive( Steer, MoveTarget );
}
else
{
//Otherwise use a seek steering mechanism.
SteerSeek( Steer, MoveTarget );
}
}
#if FEELER_COLLISION
Vector f, u, l, r, d;
float probeLength = GetAbsVelocity().Length();
if ( probeLength < 150 )
probeLength = 150;
if ( probeLength > 500 )
probeLength = 500;
f = DoProbe( GetLocalOrigin() + (probeLength * forward) );
r = DoProbe( GetLocalOrigin() + (probeLength/3 * (forward+right)) );
l = DoProbe( GetLocalOrigin() + (probeLength/3 * (forward-right)) );
u = DoProbe( GetLocalOrigin() + (probeLength/3 * (forward+up)) );
d = DoProbe( GetLocalOrigin() + (probeLength/3 * (forward-up)) );
SteerAvoid = f+r+l+u+d;
//NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin()+SteerAvoid, 255, 255, 0, false, 0.1f );
if ( SteerAvoid.LengthSqr() )
{
Steer = (SteerAvoid*0.5f);
}
m_vecVelocity = m_vecVelocity + (Steer*0.5f);
VectorNormalize( m_vecVelocity );
SteerRel.x = forward.Dot( m_vecVelocity );
SteerRel.y = right.Dot( m_vecVelocity );
SteerRel.z = up.Dot( m_vecVelocity );
m_vecVelocity *= m_flGroundSpeed;
#else
//See if we need to avoid any obstacles.
if ( SteerAvoidObstacles( SteerAvoid, GetAbsVelocity(), forward, right, up ) )
{
//Take the avoidance vector
Steer = SteerAvoid;
}
//Clamp our ideal steering vector to within our physical limitations.
ClampSteer( Steer, SteerRel, forward, right, up );
ApplyAbsVelocityImpulse( Steer * flInterval );
#endif
Vector vecNewVelocity = GetAbsVelocity();
float flLength = vecNewVelocity.Length();
//Clamp our final speed
if ( flLength > m_flGroundSpeed )
{
vecNewVelocity *= ( m_flGroundSpeed / flLength );
flLength = m_flGroundSpeed;
}
Vector workVelocity = vecNewVelocity;
AddSwimNoise( &workVelocity );
// Pose the fish properly
SetPoses( SteerRel, flLength );
//Drag our victim before moving
if ( m_pVictim != NULL )
{
DragVictim( (workVelocity*flInterval).Length() );
}
//Move along the current velocity vector
if ( WalkMove( workVelocity * flInterval, MASK_NPCSOLID ) == false )
{
//Attempt a half-step
if ( WalkMove( (workVelocity*0.5f) * flInterval, MASK_NPCSOLID) == false )
{
//Restart the velocity
//VectorNormalize( m_vecVelocity );
vecNewVelocity *= 0.5f;
}
else
{
//Cut our velocity in half
vecNewVelocity *= 0.5f;
}
}
SetAbsVelocity( vecNewVelocity );
}
//-----------------------------------------------------------------------------
// Purpose: Gets a steering vector to arrive at a target location with a
// relatively small velocity.
// Input : Steer - Receives the ideal steering vector.
// Target - Target position at which to arrive.
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::SteerArrive(Vector &Steer, const Vector &Target)
{
Vector Offset = Target - GetLocalOrigin();
float fTargetDistance = Offset.Length();
float fIdealSpeed = m_flGroundSpeed * (fTargetDistance / ICH_WAYPOINT_DISTANCE);
float fClippedSpeed = MIN( fIdealSpeed, m_flGroundSpeed );
Vector DesiredVelocity( 0, 0, 0 );
if ( fTargetDistance > ICH_WAYPOINT_DISTANCE )
{
DesiredVelocity = (fClippedSpeed / fTargetDistance) * Offset;
}
Steer = DesiredVelocity - GetAbsVelocity();
}
//-----------------------------------------------------------------------------
// Purpose: Gets a steering vector to move towards a target position as quickly
// as possible.
// Input : Steer - Receives the ideal steering vector.
// Target - Target position to seek.
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::SteerSeek( Vector &Steer, const Vector &Target )
{
Vector offset = Target - GetLocalOrigin();
VectorNormalize( offset );
Vector DesiredVelocity = m_flGroundSpeed * offset;
Steer = DesiredVelocity - GetAbsVelocity();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &Steer -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_Ichthyosaur::SteerAvoidObstacles(Vector &Steer, const Vector &Velocity, const Vector &Forward, const Vector &Right, const Vector &Up)
{
trace_t tr;
bool collided = false;
Vector dir = Velocity;
float speed = VectorNormalize( dir );
//Look ahead one second and avoid whatever is in our way.
AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + (dir*speed), GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
Vector forward;
GetVectors( &forward, NULL, NULL );
//If we're hitting our enemy, just continue on
if ( ( GetEnemy() != NULL ) && ( tr.m_pEnt == GetEnemy() ) )
return false;
if ( tr.fraction < 1.0f )
{
CBaseEntity *pBlocker = tr.m_pEnt;
if ( ( pBlocker != NULL ) && ( pBlocker->MyNPCPointer() != NULL ) )
{
DevMsg( 2, "Avoiding an NPC\n" );
Vector HitOffset = tr.endpos - GetAbsOrigin();
Vector SteerUp = CrossProduct( HitOffset, Velocity );
Steer = CrossProduct( SteerUp, Velocity );
VectorNormalize( Steer );
/*Vector probeDir = tr.endpos - GetAbsOrigin();
Vector normalToProbeAndWallNormal = probeDir.Cross( tr.plane.normal );
Steer = normalToProbeAndWallNormal.Cross( probeDir );
VectorNormalize( Steer );*/
if ( tr.fraction > 0 )
{
Steer = (Steer * Velocity.Length()) / tr.fraction;
//NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin()+Steer, 255, 0, 0, false, 0.1f );
}
else
{
Steer = (Steer * 1000 * Velocity.Length());
//NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin()+Steer, 255, 0, 0, false, 0.1f );
}
}
else
{
if ( ( pBlocker != NULL ) && ( pBlocker == GetEnemy() ) )
{
DevMsg( "Avoided collision\n" );
return false;
}
DevMsg( 2, "Avoiding the world\n" );
Vector steeringVector = tr.plane.normal;
if ( tr.fraction == 0.0f )
return false;
Steer = steeringVector * ( Velocity.Length() / tr.fraction );
//NDebugOverlay::Line( GetLocalOrigin(), GetLocalOrigin()+Steer, 255, 0, 0, false, 0.1f );
}
//return true;
collided = true;
}
//Try to remain 8 feet above the ground.
AI_TraceLine( GetAbsOrigin(), GetAbsOrigin() + Vector(0, 0, -ICH_HEIGHT_PREFERENCE), MASK_NPCSOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
if ( tr.fraction < 1.0f )
{
Steer += Vector( 0, 0, m_vecAccelerationMax.z / tr.fraction );
collided = true;
}
//Stay under the surface
if ( m_bIgnoreSurface == false )
{
float waterLevel = ( UTIL_WaterLevel( GetAbsOrigin(), GetAbsOrigin().z, GetAbsOrigin().z+ICH_DEPTH_PREFERENCE ) - GetAbsOrigin().z ) / ICH_DEPTH_PREFERENCE;
if ( waterLevel < 1.0f )
{
Steer += -Vector( 0, 0, m_vecAccelerationMax.z / waterLevel );
collided = true;
}
}
return collided;
}
//-----------------------------------------------------------------------------
// Purpose: Clamps the desired steering vector based on the limitations of this
// vehicle.
// Input : SteerAbs - The vector indicating our ideal steering vector. Receives
// the clamped steering vector in absolute (x,y,z) coordinates.
// SteerRel - Receives the clamped steering vector in relative (forward, right, up)
// coordinates.
// forward - Our current forward vector.
// right - Our current right vector.
// up - Our current up vector.
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::ClampSteer(Vector &SteerAbs, Vector &SteerRel, Vector &forward, Vector &right, Vector &up)
{
float fForwardSteer = DotProduct(SteerAbs, forward);
float fRightSteer = DotProduct(SteerAbs, right);
float fUpSteer = DotProduct(SteerAbs, up);
if (fForwardSteer > 0)
{
fForwardSteer = MIN(fForwardSteer, m_vecAccelerationMax.x);
}
else
{
fForwardSteer = MAX(fForwardSteer, m_vecAccelerationMin.x);
}
if (fRightSteer > 0)
{
fRightSteer = MIN(fRightSteer, m_vecAccelerationMax.y);
}
else
{
fRightSteer = MAX(fRightSteer, m_vecAccelerationMin.y);
}
if (fUpSteer > 0)
{
fUpSteer = MIN(fUpSteer, m_vecAccelerationMax.z);
}
else
{
fUpSteer = MAX(fUpSteer, m_vecAccelerationMin.z);
}
SteerAbs = (fForwardSteer*forward) + (fRightSteer*right) + (fUpSteer*up);
SteerRel.x = fForwardSteer;
SteerRel.y = fRightSteer;
SteerRel.z = fUpSteer;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pTargetEnt -
// vecDir -
// flDistance -
// flInterval -
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::MoveFlyExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flDistance, float flInterval )
{
IchthyosaurMoveType_t eMoveType = ( GetNavigator()->CurWaypointIsGoal() ) ? ICH_MOVETYPE_ARRIVE : ICH_MOVETYPE_SEEK;
m_flGroundSpeed = GetGroundSpeed();
Vector moveGoal = GetNavigator()->GetCurWaypointPos();
//See if we can move directly to our goal
if ( ( GetEnemy() != NULL ) && ( GetNavigator()->GetGoalTarget() == (CBaseEntity *) GetEnemy() ) )
{
trace_t tr;
Vector goalPos = GetEnemy()->GetAbsOrigin() + ( GetEnemy()->GetSmoothedVelocity() * 0.5f );
AI_TraceHull( GetAbsOrigin(), goalPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, GetEnemy(), COLLISION_GROUP_NONE, &tr );
if ( tr.fraction == 1.0f )
{
moveGoal = tr.endpos;
}
}
//Move
DoMovement( flInterval, moveGoal, eMoveType );
//Save the info from that run
m_vecLastMoveTarget = moveGoal;
m_bHasMoveTarget = true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pEntity -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_Ichthyosaur::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker )
{
// don't look through water
if ( GetWaterLevel() != pEntity->GetWaterLevel() )
return false;
return BaseClass::FVisible( pEntity, traceMask, ppBlocker );
}
//-----------------------------------------------------------------------------
// Purpose: Get our conditions for a melee attack
// Input : flDot -
// flDist -
// Output : int
//-----------------------------------------------------------------------------
int CNPC_Ichthyosaur::MeleeAttack1Conditions( float flDot, float flDist )
{
Vector predictedDir = ( (GetEnemy()->GetAbsOrigin()+(GetEnemy()->GetSmoothedVelocity())) - GetAbsOrigin() );
float flPredictedDist = VectorNormalize( predictedDir );
Vector vBodyDir;
GetVectors( &vBodyDir, NULL, NULL );
float flPredictedDot = DotProduct( predictedDir, vBodyDir );
if ( flPredictedDot < 0.8f )
return COND_NOT_FACING_ATTACK;
if ( ( flPredictedDist > ( GetAbsVelocity().Length() * 0.5f) ) && ( flDist > 128.0f ) )
return COND_TOO_FAR_TO_ATTACK;
return COND_CAN_MELEE_ATTACK1;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pEvent -
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::HandleAnimEvent( animevent_t *pEvent )
{
switch ( pEvent->event )
{
case ICH_AE_BITE:
Bite();
break;
case ICH_AE_BITE_START:
{
EmitSound( "NPC_Ichthyosaur.AttackGrowl" );
}
break;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::Bite( void )
{
//Don't allow another bite too soon
if ( m_flNextBiteTime > gpGlobals->curtime )
return;
CBaseEntity *pHurt;
//FIXME: E3 HACK - Always damage bullseyes if we're scripted to hit them
if ( ( GetEnemy() != NULL ) && ( GetEnemy()->Classify() == CLASS_BULLSEYE ) )
{
pHurt = GetEnemy();
}
else
{
pHurt = CheckTraceHullAttack( 108, Vector(-32,-32,-32), Vector(32,32,32), 0, DMG_CLUB );
}
//Hit something
if ( pHurt != NULL )
{
CTakeDamageInfo info( this, this, sk_ichthyosaur_melee_dmg.GetInt(), DMG_CLUB );
if ( pHurt->IsPlayer() )
{
CBasePlayer *pPlayer = ToBasePlayer( pHurt );
if ( pPlayer )
{
if ( ( ( m_flHoldTime < gpGlobals->curtime ) && ( pPlayer->m_iHealth < (pPlayer->m_iMaxHealth*0.5f)) ) || ( pPlayer->GetWaterLevel() < 1 ) )
{
//EnsnareVictim( pHurt );
}
else
{
info.SetDamage( sk_ichthyosaur_melee_dmg.GetInt() * 3 );
}
CalculateMeleeDamageForce( &info, GetAbsVelocity(), pHurt->GetAbsOrigin() );
pHurt->TakeDamage( info );
color32 red = {64, 0, 0, 255};
UTIL_ScreenFade( pPlayer, red, 0.5, 0, FFADE_IN );
//Disorient the player
QAngle angles = pPlayer->GetLocalAngles();
angles.x += random->RandomInt( 60, 25 );
angles.y += random->RandomInt( 60, 25 );
angles.z = 0.0f;
pPlayer->SetLocalAngles( angles );
pPlayer->SnapEyeAngles( angles );
}
}
else
{
CalculateMeleeDamageForce( &info, GetAbsVelocity(), pHurt->GetAbsOrigin() );
pHurt->TakeDamage( info );
}
m_flNextBiteTime = gpGlobals->curtime + random->RandomFloat( 2.0f, 4.0f );
//Bubbles!
UTIL_Bubbles( pHurt->GetAbsOrigin()+Vector(-32.0f,-32.0f,-32.0f), pHurt->GetAbsOrigin()+Vector(32.0f,32.0f,0.0f), random->RandomInt( 16, 32 ) );
// Play a random attack hit sound
EmitSound( "NPC_Ichthyosaur.Bite" );
if ( GetActivity() == ACT_MELEE_ATTACK1 )
{
SetActivity( (Activity) ACT_ICH_BITE_HIT );
}
return;
}
//Play the miss animation and sound
if ( GetActivity() == ACT_MELEE_ATTACK1 )
{
SetActivity( (Activity) ACT_ICH_BITE_MISS );
}
//Miss sound
EmitSound( "NPC_Ichthyosaur.BiteMiss" );
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_Ichthyosaur::Beached( void )
{
trace_t tr;
Vector testPos;
testPos = GetAbsOrigin() - Vector( 0, 0, ICH_DEPTH_PREFERENCE );
AI_TraceHull( GetAbsOrigin(), testPos, GetHullMins(), GetHullMaxs(), MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
return ( tr.fraction < 1.0f );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::PrescheduleThink( void )
{
BaseClass::PrescheduleThink();
//Ambient sounds
/*
if ( random->RandomInt( 0, 20 ) == 10 )
{
if ( random->RandomInt( 0, 1 ) )
{
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pSwimSound, random->RandomFloat( 0.0f, 0.5f ), 1.0f );
}
else
{
ENVELOPE_CONTROLLER.SoundChangeVolume( m_pVoiceSound, random->RandomFloat( 0.0f, 0.5f ), 1.0f );
}
}
*/
//Pings
if ( m_flNextPingTime < gpGlobals->curtime )
{
m_flNextPingTime = gpGlobals->curtime + random->RandomFloat( 3.0f, 8.0f );
}
//Growls
if ( ( m_NPCState == NPC_STATE_COMBAT || m_NPCState == NPC_STATE_ALERT ) && ( m_flNextGrowlTime < gpGlobals->curtime ) )
{
m_flNextGrowlTime = gpGlobals->curtime + random->RandomFloat( 2.0f, 6.0f );
}
//Randomly emit bubbles
if ( random->RandomInt( 0, 10 ) == 0 )
{
UTIL_Bubbles( GetAbsOrigin()+(GetHullMins()*0.5f), GetAbsOrigin()+(GetHullMaxs()*0.5f), 1 );
}
//Check our water level
if ( GetWaterLevel() != 3 )
{
if ( GetWaterLevel() < 2 )
{
DevMsg( 2, "Came out of water\n" );
if ( Beached() )
{
SetSchedule( SCHED_ICH_THRASH );
Vector vecNewVelocity = GetAbsVelocity();
vecNewVelocity[2] = 8.0f;
SetAbsVelocity( vecNewVelocity );
}
}
else
{
//TODO: Wake effects
}
}
//If we have a victim, update them
if ( m_pVictim != NULL )
{
//See if it's time to release the victim
if ( m_flHoldTime < gpGlobals->curtime )
{
ReleaseVictim();
return;
}
Bite();
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pevInflictor -
// *pAttacker -
// flDamage -
// bitsDamageType -
//-----------------------------------------------------------------------------
int CNPC_Ichthyosaur::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
//Release the player if he's struck us while being held
if ( m_flHoldTime > gpGlobals->curtime )
{
ReleaseVictim();
//Don't give them as much time to flee
m_flNextBiteTime = gpGlobals->curtime + 2.0f;
SetSchedule( SCHED_ICH_THRASH );
}
return BaseClass::OnTakeDamage_Alive( info );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::EnsnareVictim( CBaseEntity *pVictim )
{
CBaseCombatCharacter* pBCC = (CBaseCombatCharacter *) pVictim;
if ( pBCC && pBCC->DispatchInteraction( g_interactionBarnacleVictimGrab, NULL, this ) )
{
if ( pVictim->IsPlayer() )
{
CBasePlayer *pPlayer = dynamic_cast< CBasePlayer * >((CBaseEntity *) pVictim);
if ( pPlayer )
{
m_flHoldTime = MAX( gpGlobals->curtime+3.0f, pPlayer->PlayerDrownTime() - 2.0f );
}
}
else
{
m_flHoldTime = gpGlobals->curtime + 4.0f;
}
m_pVictim = pVictim;
m_pVictim->AddSolidFlags( FSOLID_NOT_SOLID );
SetSchedule( SCHED_ICH_DROWN_VICTIM );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::ReleaseVictim( void )
{
CBaseCombatCharacter *pBCC = (CBaseCombatCharacter *) m_pVictim;
pBCC->DispatchInteraction( g_interactionBarnacleVictimReleased, NULL, this );
m_pVictim->RemoveSolidFlags( FSOLID_NOT_SOLID );
m_pVictim = NULL;
m_flNextBiteTime = gpGlobals->curtime + 8.0f;
m_flHoldTime = gpGlobals->curtime - 0.1f;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : speed to move at
//-----------------------------------------------------------------------------
float CNPC_Ichthyosaur::GetGroundSpeed( void )
{
if ( m_flHoldTime > gpGlobals->curtime )
return ICH_SWIM_SPEED_WALK/2.0f;
if ( GetIdealActivity() == ACT_WALK )
return ICH_SWIM_SPEED_WALK;
if ( GetIdealActivity() == ACT_ICH_THRASH )
return ICH_SWIM_SPEED_WALK;
return ICH_SWIM_SPEED_RUN;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : type -
// Output : int
//-----------------------------------------------------------------------------
int CNPC_Ichthyosaur::TranslateSchedule( int type )
{
if ( type == SCHED_CHASE_ENEMY ) return SCHED_ICH_CHASE_ENEMY;
//if ( type == SCHED_IDLE_STAND ) return SCHED_PATROL_WALK;
if ( type == SCHED_PATROL_RUN ) return SCHED_ICH_PATROL_RUN;
if ( type == SCHED_PATROL_WALK ) return SCHED_ICH_PATROL_WALK;
if ( type == SCHED_MELEE_ATTACK1 ) return SCHED_ICH_MELEE_ATTACK1;
return BaseClass::TranslateSchedule( type );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pTask -
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::StartTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_ICH_THRASH_PATH:
GetNavigator()->SetMovementActivity( (Activity) ACT_ICH_THRASH );
TaskComplete();
break;
case TASK_ICH_GET_PATH_TO_RANDOM_NODE:
{
if ( GetEnemy() == NULL || !GetNavigator()->SetRandomGoal( GetEnemy()->GetLocalOrigin(), pTask->flTaskData ) )
{
if (!GetNavigator()->SetRandomGoal( pTask->flTaskData ) )
{
TaskFail(FAIL_NO_REACHABLE_NODE);
return;
}
}
TaskComplete();
}
break;
case TASK_ICH_GET_PATH_TO_DROWN_NODE:
{
Vector drownPos = GetLocalOrigin() - Vector( 0, 0, pTask->flTaskData );
if ( GetNavigator()->SetGoal( drownPos, AIN_CLEAR_TARGET ) == false )
{
TaskFail( FAIL_NO_ROUTE );
return;
}
TaskComplete();
}
break;
case TASK_MELEE_ATTACK1:
m_flPlaybackRate = 1.0f;
BaseClass::StartTask(pTask);
break;
default:
BaseClass::StartTask(pTask);
break;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pTask -
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::RunTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_ICH_GET_PATH_TO_RANDOM_NODE:
return;
break;
case TASK_ICH_GET_PATH_TO_DROWN_NODE:
return;
break;
default:
BaseClass::RunTask(pTask);
break;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : desired yaw speed
//-----------------------------------------------------------------------------
float CNPC_Ichthyosaur::MaxYawSpeed( void )
{
if ( GetIdealActivity() == ACT_MELEE_ATTACK1 )
return 16.0f;
if ( GetIdealActivity() == ACT_ICH_THRASH )
return 16.0f;
//Ramp up the yaw speed as we increase our speed
return ICH_MIN_TURN_SPEED + ( (ICH_MAX_TURN_SPEED-ICH_MIN_TURN_SPEED) * ( fabs(GetAbsVelocity().Length()) / ICH_SWIM_SPEED_RUN ) );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pEnemy -
// &chasePosition -
// &tolerance -
//-----------------------------------------------------------------------------
void CNPC_Ichthyosaur::TranslateNavGoal( CBaseEntity *pEnemy, Vector &chasePosition )
{
Vector offset = pEnemy->EyePosition() - pEnemy->GetAbsOrigin();
chasePosition += offset;
}
float CNPC_Ichthyosaur::GetDefaultNavGoalTolerance()
{
return GetHullWidth()*2.0f;
}
//-----------------------------------------------------------------------------
//
// Schedules
//
//-----------------------------------------------------------------------------
//==================================================
// SCHED_ICH_CHASE_ENEMY
//==================================================
AI_DEFINE_SCHEDULE
(
SCHED_ICH_CHASE_ENEMY,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_ICH_PATROL_WALK"
" TASK_SET_TOLERANCE_DISTANCE 64"
" TASK_SET_GOAL GOAL:ENEMY"
" TASK_GET_PATH_TO_GOAL PATH:TRAVEL"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_ENEMY_DEAD"
" COND_ENEMY_UNREACHABLE"
" COND_CAN_MELEE_ATTACK1"
" COND_TOO_CLOSE_TO_ATTACK"
" COND_LOST_ENEMY"
" COND_TASK_FAILED"
);
//==================================================
// SCHED_ICH_PATROL_RUN
//==================================================
AI_DEFINE_SCHEDULE
(
SCHED_ICH_PATROL_RUN,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE"
" TASK_SET_TOLERANCE_DISTANCE 64"
" TASK_SET_ROUTE_SEARCH_TIME 4"
" TASK_ICH_GET_PATH_TO_RANDOM_NODE 200"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
""
" Interrupts"
" COND_CAN_MELEE_ATTACK1"
" COND_GIVE_WAY"
" COND_NEW_ENEMY"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
);
//==================================================
// SCHED_ICH_PATROL_WALK
//==================================================
AI_DEFINE_SCHEDULE
(
SCHED_ICH_PATROL_WALK,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE"
" TASK_SET_TOLERANCE_DISTANCE 64"
" TASK_SET_ROUTE_SEARCH_TIME 4"
" TASK_ICH_GET_PATH_TO_RANDOM_NODE 200"
" TASK_WALK_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
""
" Interrupts"
" COND_CAN_MELEE_ATTACK1"
" COND_GIVE_WAY"
" COND_NEW_ENEMY"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
);
//==================================================
// SCHED_ICH_DROWN_VICTIM
//==================================================
AI_DEFINE_SCHEDULE
(
SCHED_ICH_DROWN_VICTIM,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE"
" TASK_SET_TOLERANCE_DISTANCE 64"
" TASK_SET_ROUTE_SEARCH_TIME 4"
" TASK_ICH_GET_PATH_TO_DROWN_NODE 256"
" TASK_WALK_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_LIGHT_DAMAGE"
" COND_HEAVY_DAMAGE"
);
//=========================================================
// SCHED_ICH_MELEE_ATTACK1
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_ICH_MELEE_ATTACK1,
" Tasks"
" TASK_ANNOUNCE_ATTACK 1"
" TASK_MELEE_ATTACK1 0"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_ENEMY_DEAD"
" COND_ENEMY_OCCLUDED"
);
//==================================================
// SCHED_ICH_THRASH
//==================================================
AI_DEFINE_SCHEDULE
(
SCHED_ICH_THRASH,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_COMBAT_FACE"
" TASK_SET_TOLERANCE_DISTANCE 64"
" TASK_SET_ROUTE_SEARCH_TIME 4"
" TASK_ICH_GET_PATH_TO_RANDOM_NODE 64"
" TASK_ICH_THRASH_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
""
" Interrupts"
);