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.
1395 lines
36 KiB
1395 lines
36 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Houndeye - a spooky sonic dog. |
|
// |
|
// $NoKeywords: $ |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "npc_houndeye.h" |
|
#include "ai_default.h" |
|
#include "ai_node.h" |
|
#include "ai_route.h" |
|
#include "AI_Navigator.h" |
|
#include "AI_Motor.h" |
|
#include "ai_squad.h" |
|
#include "AI_TacticalServices.h" |
|
#include "soundent.h" |
|
#include "EntityList.h" |
|
#include "game.h" |
|
#include "activitylist.h" |
|
#include "hl2_shareddefs.h" |
|
#include "grenade_energy.h" |
|
#include "energy_wave.h" |
|
#include "ai_interactions.h" |
|
#include "ndebugoverlay.h" |
|
#include "npcevent.h" |
|
#include "player.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" |
|
|
|
#define HOUNDEYE_MAX_ATTACK_RADIUS 500 |
|
#define HOUNDEYE_MIN_ATTACK_RADIUS 100 |
|
|
|
#define HOUNDEYE_EYE_FRAMES 4 // how many different switchable maps for the eye |
|
|
|
ConVar sk_Houndeye_health( "sk_Houndeye_health","0"); |
|
ConVar sk_Houndeye_dmg_blast( "sk_Houndeye_dmg_blast","0"); |
|
|
|
//========================================================= |
|
// Interactions |
|
//========================================================= |
|
int g_interactionHoundeyeGroupAttack = 0; |
|
int g_interactionHoundeyeGroupRetreat = 0; |
|
int g_interactionHoundeyeGroupRalley = 0; |
|
|
|
//========================================================= |
|
// Specialized Tasks |
|
//========================================================= |
|
enum |
|
{ |
|
TASK_HOUND_CLOSE_EYE = LAST_SHARED_TASK, |
|
TASK_HOUND_OPEN_EYE, |
|
TASK_HOUND_THREAT_DISPLAY, |
|
TASK_HOUND_FALL_ASLEEP, |
|
TASK_HOUND_WAKE_UP, |
|
TASK_HOUND_HOP_BACK, |
|
|
|
TASK_HOUND_GET_PATH_TO_CIRCLE, |
|
TASK_HOUND_REVERSE_STRAFE_DIR, |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Custom Conditions |
|
//----------------------------------------------------------------------------- |
|
enum Houndeye_Conds |
|
{ |
|
COND_HOUND_GROUP_ATTACK = LAST_SHARED_CONDITION, |
|
COND_HOUND_GROUP_RETREAT, |
|
COND_HOUND_GROUP_RALLEY, |
|
}; |
|
|
|
//========================================================= |
|
// Specialized Shedules |
|
//========================================================= |
|
enum |
|
{ |
|
SCHED_HOUND_AGITATED = LAST_SHARED_SCHEDULE, |
|
SCHED_HOUND_HOP_RETREAT, |
|
SCHED_HOUND_RANGE_ATTACK1, |
|
|
|
SCHED_HOUND_ATTACK_STRAFE, |
|
SCHED_HOUND_ATTACK_STRAFE_REVERSE, |
|
SCHED_HOUND_GROUP_ATTACK, |
|
SCHED_HOUND_GROUP_RETREAT, |
|
SCHED_HOUND_CHASE_ENEMY, |
|
SCHED_HOUND_COVER_WAIT, |
|
SCHED_HOUND_GROUP_RALLEY, |
|
}; |
|
|
|
//========================================================= |
|
// Specialized activities |
|
//========================================================= |
|
int ACT_HOUND_GUARD; |
|
|
|
//========================================================= |
|
// Monster's Anim Events Go Here |
|
//========================================================= |
|
#define HOUND_AE_WARN 1 |
|
#define HOUND_AE_STARTATTACK 2 |
|
#define HOUND_AE_THUMP 3 |
|
#define HOUND_AE_ANGERSOUND1 4 |
|
#define HOUND_AE_ANGERSOUND2 5 |
|
#define HOUND_AE_HOPBACK 6 |
|
#define HOUND_AE_CLOSE_EYE 7 |
|
#define HOUND_AE_LEAP_HIT 8 |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Initialize the custom schedules |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Houndeye::InitCustomSchedules(void) |
|
{ |
|
INIT_CUSTOM_AI(CNPC_Houndeye); |
|
|
|
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_CLOSE_EYE); |
|
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_OPEN_EYE); |
|
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_THREAT_DISPLAY); |
|
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_FALL_ASLEEP); |
|
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_WAKE_UP); |
|
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_HOP_BACK); |
|
|
|
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_GET_PATH_TO_CIRCLE); |
|
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_REVERSE_STRAFE_DIR); |
|
|
|
ADD_CUSTOM_CONDITION(CNPC_Houndeye, COND_HOUND_GROUP_ATTACK); |
|
ADD_CUSTOM_CONDITION(CNPC_Houndeye, COND_HOUND_GROUP_RETREAT); |
|
|
|
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_HOP_RETREAT); |
|
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_RANGE_ATTACK1); |
|
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_ATTACK_STRAFE); |
|
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_ATTACK_STRAFE_REVERSE); |
|
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_GROUP_ATTACK); |
|
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_GROUP_RETREAT); |
|
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_CHASE_ENEMY); |
|
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_COVER_WAIT); |
|
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_GROUP_RALLEY); |
|
|
|
ADD_CUSTOM_ACTIVITY(CNPC_Houndeye, ACT_HOUND_GUARD); |
|
|
|
g_interactionHoundeyeGroupAttack = CBaseCombatCharacter::GetInteractionID(); |
|
g_interactionHoundeyeGroupRetreat = CBaseCombatCharacter::GetInteractionID(); |
|
g_interactionHoundeyeGroupRalley = CBaseCombatCharacter::GetInteractionID(); |
|
|
|
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_HOP_RETREAT); |
|
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_RANGE_ATTACK1); |
|
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_ATTACK_STRAFE); |
|
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_ATTACK_STRAFE_REVERSE); |
|
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_GROUP_ATTACK); |
|
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_GROUP_RETREAT); |
|
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_CHASE_ENEMY); |
|
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_COVER_WAIT); |
|
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_GROUP_RALLEY); |
|
} |
|
|
|
LINK_ENTITY_TO_CLASS( npc_houndeye, CNPC_Houndeye ); |
|
IMPLEMENT_CUSTOM_AI( npc_houndeye, CNPC_Houndeye ); |
|
|
|
BEGIN_DATADESC( CNPC_Houndeye ) |
|
|
|
DEFINE_FIELD( m_fAsleep, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_fDontBlink, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_flNextSecondaryAttack, FIELD_TIME ), |
|
DEFINE_FIELD( m_bLoopClockwise, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( m_pEnergyWave, FIELD_CLASSPTR ), |
|
DEFINE_FIELD( m_flEndEnergyWaveTime, FIELD_TIME ), |
|
|
|
END_DATADESC() |
|
|
|
//========================================================= |
|
// Classify - indicates this monster's place in the |
|
// relationship table. |
|
//========================================================= |
|
Class_T CNPC_Houndeye::Classify ( void ) |
|
{ |
|
return CLASS_HOUNDEYE; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
int CNPC_Houndeye::RangeAttack1Conditions ( float flDot, float flDist ) |
|
{ |
|
// I'm not allowed to attack if standing in another hound eye |
|
// (note houndeyes allowed to interpenetrate) |
|
trace_t tr; |
|
AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,0.1), |
|
GetHullMins(), GetHullMaxs(), |
|
MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); |
|
if (tr.startsolid) |
|
{ |
|
CBaseEntity *pEntity = tr.m_pEnt; |
|
if (pEntity->Classify() == CLASS_HOUNDEYE) |
|
{ |
|
return( COND_NONE ); |
|
} |
|
} |
|
|
|
// If I'm really close to my enemy allow me to attack if |
|
// I'm facing regardless of next attack time |
|
if (flDist < 100 && flDot >= 0.3) |
|
{ |
|
return COND_CAN_RANGE_ATTACK1; |
|
} |
|
if ( gpGlobals->curtime < m_flNextAttack ) |
|
{ |
|
return( COND_NONE ); |
|
} |
|
if (flDist > ( HOUNDEYE_MAX_ATTACK_RADIUS * 0.5 )) |
|
{ |
|
return COND_TOO_FAR_TO_ATTACK; |
|
} |
|
if (flDot < 0.3) |
|
{ |
|
return COND_NOT_FACING_ATTACK; |
|
} |
|
return COND_CAN_RANGE_ATTACK1; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Overidden for human grunts because they hear the DANGER sound |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
int CNPC_Houndeye::GetSoundInterests( void ) |
|
{ |
|
return SOUND_WORLD | |
|
SOUND_COMBAT | |
|
SOUND_PLAYER | |
|
SOUND_DANGER; |
|
} |
|
|
|
//========================================================= |
|
// MaxYawSpeed - allows each sequence to have a different |
|
// turn rate associated with it. |
|
//========================================================= |
|
float CNPC_Houndeye::MaxYawSpeed ( void ) |
|
{ |
|
int ys = 90; |
|
|
|
switch ( GetActivity() ) |
|
{ |
|
case ACT_CROUCHIDLE://sleeping! |
|
ys = 0; |
|
break; |
|
case ACT_IDLE: |
|
ys = 60; |
|
break; |
|
case ACT_WALK: |
|
ys = 90; |
|
break; |
|
case ACT_RUN: |
|
ys = 90; |
|
break; |
|
case ACT_TURN_LEFT: |
|
case ACT_TURN_RIGHT: |
|
ys = 90; |
|
break; |
|
} |
|
return ys; |
|
} |
|
|
|
//========================================================= |
|
// HandleAnimEvent - catches the monster-specific messages |
|
// that occur when tagged animation frames are played. |
|
//========================================================= |
|
void CNPC_Houndeye::HandleAnimEvent( animevent_t *pEvent ) |
|
{ |
|
switch ( pEvent->event ) |
|
{ |
|
case HOUND_AE_WARN: |
|
// do stuff for this event. |
|
WarnSound(); |
|
break; |
|
|
|
case HOUND_AE_STARTATTACK: |
|
WarmUpSound(); |
|
break; |
|
|
|
case HOUND_AE_HOPBACK: |
|
{ |
|
float flGravity = GetCurrentGravity(); |
|
|
|
SetGroundEntity( NULL ); |
|
|
|
Vector forward; |
|
AngleVectors( GetLocalAngles(), &forward ); |
|
Vector vecNewVelocity = forward * -200; |
|
//jump up 36 inches |
|
vecNewVelocity.z += sqrt( 2 * flGravity * 36 ); |
|
SetAbsVelocity( vecNewVelocity ); |
|
break; |
|
} |
|
|
|
case HOUND_AE_THUMP: |
|
// emit the shockwaves |
|
SonicAttack(); |
|
m_flNextAttack = gpGlobals->curtime + random->RandomFloat( 5.0, 8.0 ); |
|
break; |
|
|
|
case HOUND_AE_ANGERSOUND1: |
|
{ |
|
EmitSound( "NPC_Houndeye.Anger1" ); |
|
} |
|
break; |
|
|
|
case HOUND_AE_ANGERSOUND2: |
|
{ |
|
EmitSound( "NPC_Houndeye.Anger2" ); |
|
} |
|
break; |
|
|
|
case HOUND_AE_CLOSE_EYE: |
|
if ( !m_fDontBlink ) |
|
{ |
|
//<<TEMP>> pev->skin = HOUNDEYE_EYE_FRAMES - 1; |
|
} |
|
break; |
|
|
|
case HOUND_AE_LEAP_HIT: |
|
{ |
|
//<<TEMP>>return;//<<TEMP>> |
|
SetGroundEntity( NULL ); |
|
|
|
// |
|
// Take him off ground so engine doesn't instantly reset FL_ONGROUND. |
|
// |
|
UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 )); |
|
Vector vecJumpDir; |
|
if ( GetEnemy() != NULL ) |
|
{ |
|
Vector vecEnemyEyePos = GetEnemy()->EyePosition(); |
|
|
|
float gravity = GetCurrentGravity(); |
|
if ( gravity <= 1 ) |
|
{ |
|
gravity = 1; |
|
} |
|
|
|
// |
|
// How fast does the houndeye need to travel to reach my enemy's eyes given gravity? |
|
// |
|
float height = ( vecEnemyEyePos.z - GetAbsOrigin().z ); |
|
if ( height < 16 ) |
|
{ |
|
height = 16; |
|
} |
|
else if ( height > 120 ) |
|
{ |
|
height = 120; |
|
} |
|
float speed = sqrt( 2 * gravity * height ); |
|
float time = speed / gravity; |
|
|
|
// |
|
// Scale the sideways velocity to get there at the right time |
|
// |
|
vecJumpDir = vecEnemyEyePos - GetAbsOrigin(); |
|
vecJumpDir = vecJumpDir / time; |
|
|
|
// |
|
// Speed to offset gravity at the desired height. |
|
// |
|
vecJumpDir.z = speed; |
|
|
|
// |
|
// Don't jump too far/fast. |
|
// |
|
float distance = vecJumpDir.Length(); |
|
if ( distance > 650 ) |
|
{ |
|
vecJumpDir = vecJumpDir * ( 650.0 / distance ); |
|
} |
|
} |
|
else |
|
{ |
|
Vector forward, up; |
|
AngleVectors( GetLocalAngles(), &forward, NULL, &up ); |
|
// |
|
// Jump hop, don't care where. |
|
// |
|
vecJumpDir = Vector( forward.x, forward.y, up.z ) * 350; |
|
} |
|
|
|
SetAbsVelocity( vecJumpDir ); |
|
m_flNextAttack = gpGlobals->curtime + 2; |
|
break; |
|
} |
|
default: |
|
BaseClass::HandleAnimEvent( pEvent ); |
|
break; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// Spawn |
|
//========================================================= |
|
void CNPC_Houndeye::Spawn() |
|
{ |
|
Precache( ); |
|
|
|
SetModel("models/houndeye.mdl"); |
|
SetHullType(HULL_WIDE_SHORT); |
|
SetHullSizeNormal(); |
|
|
|
SetSolid( SOLID_BBOX ); |
|
AddSolidFlags( FSOLID_NOT_STANDABLE ); |
|
SetMoveType( MOVETYPE_STEP ); |
|
SetBloodColor( BLOOD_COLOR_YELLOW ); |
|
m_iHealth = sk_Houndeye_health.GetFloat(); |
|
m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) |
|
m_NPCState = NPC_STATE_NONE; |
|
m_fAsleep = false; // everyone spawns awake |
|
m_fDontBlink = false; |
|
CapabilitiesAdd( bits_CAP_SQUAD ); |
|
CapabilitiesAdd( bits_CAP_MOVE_GROUND ); |
|
CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 ); |
|
CapabilitiesAdd( bits_CAP_TURN_HEAD ); |
|
|
|
m_flNextSecondaryAttack = 0; |
|
m_bLoopClockwise = random->RandomInt(0,1) ? true : false; |
|
|
|
m_pEnergyWave = NULL; |
|
m_flEndEnergyWaveTime = 0; |
|
|
|
SetCollisionGroup( HL2COLLISION_GROUP_HOUNDEYE ); |
|
|
|
NPCInit(); |
|
} |
|
|
|
//========================================================= |
|
// Precache - precaches all resources this monster needs |
|
//========================================================= |
|
void CNPC_Houndeye::Precache() |
|
{ |
|
PrecacheModel("models/houndeye.mdl"); |
|
|
|
PrecacheScriptSound( "NPC_Houndeye.Anger1" ); |
|
PrecacheScriptSound( "NPC_Houndeye.Anger2" ); |
|
PrecacheScriptSound( "NPC_Houndeye.SpeakSentence" ); |
|
PrecacheScriptSound( "NPC_Houndeye.Idle" ); |
|
PrecacheScriptSound( "NPC_Houndeye.WarmUp" ); |
|
PrecacheScriptSound( "NPC_Houndeye.Warn" ); |
|
PrecacheScriptSound( "NPC_Houndeye.Alert" ); |
|
PrecacheScriptSound( "NPC_Houndeye.Die" ); |
|
PrecacheScriptSound( "NPC_Houndeye.Pain" ); |
|
PrecacheScriptSound( "NPC_Houndeye.Retreat" ); |
|
PrecacheScriptSound( "NPC_Houndeye.SonicAttack" ); |
|
|
|
PrecacheScriptSound( "NPC_Houndeye.GroupAttack" ); |
|
PrecacheScriptSound( "NPC_Houndeye.GroupFollow" ); |
|
|
|
|
|
UTIL_PrecacheOther("grenade_energy"); |
|
BaseClass::Precache(); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CNPC_Houndeye::SpeakSentence( int sentenceType ) |
|
{ |
|
if (gpGlobals->curtime > m_flSoundWaitTime) |
|
{ |
|
EmitSound( "NPC_Houndeye.SpeakSentence" ); |
|
m_flSoundWaitTime = gpGlobals->curtime + 1.0; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// IdleSound |
|
//========================================================= |
|
void CNPC_Houndeye::IdleSound ( void ) |
|
{ |
|
if (!FOkToMakeSound()) |
|
{ |
|
return; |
|
} |
|
CPASAttenuationFilter filter( this,"NPC_Houndeye.Idle" ); |
|
EmitSound( filter, entindex(),"NPC_Houndeye.Idle" ); |
|
} |
|
|
|
//========================================================= |
|
// IdleSound |
|
//========================================================= |
|
void CNPC_Houndeye::WarmUpSound ( void ) |
|
{ |
|
EmitSound( "NPC_Houndeye.WarmUp" ); |
|
} |
|
|
|
//========================================================= |
|
// WarnSound |
|
//========================================================= |
|
void CNPC_Houndeye::WarnSound ( void ) |
|
{ |
|
EmitSound( "NPC_Houndeye.Warn" ); |
|
} |
|
|
|
//========================================================= |
|
// AlertSound |
|
//========================================================= |
|
void CNPC_Houndeye::AlertSound ( void ) |
|
{ |
|
// only first squad member makes ALERT sound. |
|
if ( m_pSquad && !m_pSquad->IsLeader( this ) ) |
|
{ |
|
return; |
|
} |
|
|
|
EmitSound( "NPC_Houndeye.Alert" ); |
|
} |
|
|
|
//========================================================= |
|
// DeathSound |
|
//========================================================= |
|
void CNPC_Houndeye::DeathSound ( void ) |
|
{ |
|
EmitSound( "NPC_Houndeye.Die" ); |
|
} |
|
|
|
//========================================================= |
|
// PainSound |
|
//========================================================= |
|
void CNPC_Houndeye::PainSound ( void ) |
|
{ |
|
EmitSound( "NPC_Houndeye.Pain" ); |
|
} |
|
|
|
//========================================================= |
|
// WriteBeamColor - writes a color vector to the network |
|
// based on the size of the group. |
|
//========================================================= |
|
void CNPC_Houndeye::WriteBeamColor ( void ) |
|
{ |
|
BYTE bRed, bGreen, bBlue; |
|
|
|
if ( m_pSquad ) |
|
{ |
|
switch ( m_pSquad->NumMembers() ) |
|
{ |
|
case 2: |
|
// no case for 0 or 1, cause those are impossible for monsters in Squads. |
|
bRed = 101; |
|
bGreen = 133; |
|
bBlue = 221; |
|
break; |
|
case 3: |
|
bRed = 67; |
|
bGreen = 85; |
|
bBlue = 255; |
|
break; |
|
case 4: |
|
bRed = 62; |
|
bGreen = 33; |
|
bBlue = 211; |
|
break; |
|
default: |
|
DevWarning( 2, "Unsupported Houndeye SquadSize!\n" ); |
|
bRed = 188; |
|
bGreen = 220; |
|
bBlue = 255; |
|
break; |
|
} |
|
} |
|
else |
|
{ |
|
// solo houndeye - weakest beam |
|
bRed = 188; |
|
bGreen = 220; |
|
bBlue = 255; |
|
} |
|
|
|
WRITE_BYTE( bRed ); |
|
WRITE_BYTE( bGreen ); |
|
WRITE_BYTE( bBlue ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Plays the engine sound. |
|
//----------------------------------------------------------------------------- |
|
void CNPC_Houndeye::NPCThink(void) |
|
{ |
|
if (m_pEnergyWave) |
|
{ |
|
if (gpGlobals->curtime > m_flEndEnergyWaveTime) |
|
{ |
|
UTIL_Remove(m_pEnergyWave); |
|
m_pEnergyWave = NULL; |
|
} |
|
} |
|
|
|
// ----------------------------------------------------- |
|
// Update collision group |
|
// While I'm running I'm allowed to penetrate |
|
// other houndeyes |
|
// ----------------------------------------------------- |
|
Vector vVelocity; |
|
GetVelocity( &vVelocity, NULL ); |
|
if (vVelocity.Length() > 10) |
|
{ |
|
SetCollisionGroup( HL2COLLISION_GROUP_HOUNDEYE ); |
|
} |
|
else |
|
{ |
|
// Don't go solid if resting in another houndeye |
|
trace_t tr; |
|
AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,1), |
|
GetHullMins(), GetHullMaxs(), |
|
MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); |
|
if (!tr.startsolid) |
|
{ |
|
SetCollisionGroup( COLLISION_GROUP_NONE ); |
|
} |
|
else |
|
{ |
|
SetCollisionGroup( HL2COLLISION_GROUP_HOUNDEYE ); |
|
} |
|
} |
|
/* |
|
if (GetCollisionGroup() == HL2COLLISION_GROUP_HOUNDEYE) |
|
{ |
|
NDebugOverlay::Box(GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 0, 0, 0); |
|
} |
|
else |
|
{ |
|
NDebugOverlay::Box(GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 0, 0, 0); |
|
} |
|
*/ |
|
BaseClass::NPCThink(); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : Broadcast retreat occasionally when hurt |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
int CNPC_Houndeye::OnTakeDamage_Alive( const CTakeDamageInfo &info ) |
|
{ |
|
if (m_pSquad && random->RandomInt(0,10) == 10) |
|
{ |
|
EmitSound( "NPC_Houndeye.Retreat" ); |
|
m_flSoundWaitTime = gpGlobals->curtime + 1.0; |
|
|
|
m_pSquad->BroadcastInteraction( g_interactionHoundeyeGroupRetreat, NULL, this ); |
|
} |
|
|
|
return BaseClass::OnTakeDamage_Alive( info ); |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : Broadcast retreat when member of squad killed |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CNPC_Houndeye::Event_Killed( const CTakeDamageInfo &info ) |
|
{ |
|
EmitSound( "NPC_Houndeye.Retreat" ); |
|
m_flSoundWaitTime = gpGlobals->curtime + 1.0; |
|
|
|
if (m_pSquad) |
|
{ |
|
m_pSquad->BroadcastInteraction( g_interactionHoundeyeGroupRetreat, NULL, this ); |
|
} |
|
|
|
BaseClass::Event_Killed( info ); |
|
} |
|
|
|
//========================================================= |
|
// SonicAttack |
|
//========================================================= |
|
void CNPC_Houndeye::SonicAttack ( void ) |
|
{ |
|
EmitSound( "NPC_Houndeye.SonicAttack" ); |
|
|
|
if (m_pEnergyWave) |
|
{ |
|
UTIL_Remove(m_pEnergyWave); |
|
} |
|
Vector vFacingDir = EyeDirection3D( ); |
|
m_pEnergyWave = (CEnergyWave*)Create( "energy_wave", EyePosition(), GetLocalAngles() ); |
|
m_flEndEnergyWaveTime = gpGlobals->curtime + 1; //<<TEMP>> magic |
|
m_pEnergyWave->SetAbsVelocity( 100*vFacingDir ); |
|
|
|
CBaseEntity *pEntity = NULL; |
|
// iterate on all entities in the vicinity. |
|
for ( CEntitySphereQuery sphere( GetAbsOrigin(), HOUNDEYE_MAX_ATTACK_RADIUS ); pEntity = sphere.GetCurrentEntity(); sphere.NextEntity() ) |
|
{ |
|
if (pEntity->Classify() == CLASS_HOUNDEYE) |
|
{ |
|
continue; |
|
} |
|
|
|
if (pEntity->GetFlags() & FL_NOTARGET) |
|
{ |
|
continue; |
|
} |
|
|
|
IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject(); |
|
|
|
if ( pEntity->m_takedamage != DAMAGE_NO || pPhysicsObject) |
|
{ |
|
// -------------------------- |
|
// Adjust damage by distance |
|
// -------------------------- |
|
float flDist = (pEntity->WorldSpaceCenter() - GetAbsOrigin()).Length(); |
|
float flDamageAdjuster = 1-( flDist / HOUNDEYE_MAX_ATTACK_RADIUS ); |
|
|
|
// -------------------------- |
|
// Adjust damage by direction |
|
// -------------------------- |
|
Vector forward; |
|
AngleVectors( GetAbsAngles(), &forward ); |
|
Vector vEntDir = (pEntity->GetAbsOrigin() - GetAbsOrigin()); |
|
VectorNormalize(vEntDir); |
|
float flDotPr = DotProduct(forward,vEntDir); |
|
flDamageAdjuster *= flDotPr; |
|
|
|
if (flDamageAdjuster < 0) |
|
{ |
|
continue; |
|
} |
|
|
|
// -------------------------- |
|
// Adjust damage by visibility |
|
// -------------------------- |
|
if ( !FVisible( pEntity ) ) |
|
{ |
|
if ( pEntity->IsPlayer() ) |
|
{ |
|
// if this entity is a client, and is not in full view, inflict half damage. We do this so that players still |
|
// take the residual damage if they don't totally leave the houndeye's effective radius. We restrict it to clients |
|
// so that monsters in other parts of the level don't take the damage and get pissed. |
|
flDamageAdjuster *= 0.5; |
|
} |
|
else if ( !FClassnameIs( pEntity, "func_breakable" ) && !FClassnameIs( pEntity, "func_pushable" ) ) |
|
{ |
|
// do not hurt nonclients through walls, but allow damage to be done to breakables |
|
continue; |
|
} |
|
} |
|
|
|
// ------------------------------ |
|
// Apply the damage |
|
// ------------------------------ |
|
if (pEntity->m_takedamage != DAMAGE_NO) |
|
{ |
|
CTakeDamageInfo info( this, this, flDamageAdjuster * sk_Houndeye_dmg_blast.GetFloat(), DMG_SONIC | DMG_ALWAYSGIB ); |
|
CalculateExplosiveDamageForce( &info, (pEntity->GetAbsOrigin() - GetAbsOrigin()), pEntity->GetAbsOrigin() ); |
|
|
|
pEntity->TakeDamage( info ); |
|
|
|
// Throw the player |
|
if ( pEntity->IsPlayer() ) |
|
{ |
|
Vector forward; |
|
AngleVectors( GetLocalAngles(), &forward ); |
|
|
|
Vector vecVelocity = pEntity->GetAbsVelocity(); |
|
vecVelocity += forward * 250 * flDamageAdjuster; |
|
vecVelocity.z = 300 * flDamageAdjuster; |
|
pEntity->SetAbsVelocity( vecVelocity ); |
|
pEntity->ViewPunch( QAngle(random->RandomInt(-20,20), 0, random->RandomInt(-20,20)) ); |
|
} |
|
} |
|
// ------------------------------ |
|
// Apply physics foces |
|
// ------------------------------ |
|
IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject(); |
|
if (pPhysicsObject) |
|
{ |
|
float flForce = flDamageAdjuster * 8000; |
|
pPhysicsObject->ApplyForceCenter( (vEntDir+Vector(0,0,0.2)) * flForce ); |
|
pPhysicsObject->ApplyTorqueCenter( vEntDir * flForce ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//========================================================= |
|
// start task |
|
//========================================================= |
|
void CNPC_Houndeye::StartTask( const Task_t *pTask ) |
|
{ |
|
switch ( pTask->iTask ) |
|
{ |
|
case TASK_HOUND_GET_PATH_TO_CIRCLE: |
|
{ |
|
if (GetEnemy() == NULL) |
|
{ |
|
TaskFail(FAIL_NO_ENEMY); |
|
} |
|
else |
|
{ |
|
Vector vTargetPos = GetEnemyLKP(); |
|
vTargetPos.z = GetFloorZ(vTargetPos); |
|
|
|
if (GetNavigator()->SetRadialGoal(vTargetPos, random->RandomInt(50,500), 90, 175, m_bLoopClockwise)) |
|
{ |
|
TaskComplete(); |
|
return; |
|
} |
|
TaskFail(FAIL_NO_ROUTE); |
|
} |
|
break; |
|
} |
|
case TASK_HOUND_REVERSE_STRAFE_DIR: |
|
{ |
|
// Try the other direction |
|
m_bLoopClockwise = (m_bLoopClockwise) ? false : true; |
|
TaskComplete(); |
|
break; |
|
} |
|
|
|
// Override to set appropriate distances |
|
case TASK_GET_PATH_TO_ENEMY_LOS: |
|
{ |
|
float flMaxRange = HOUNDEYE_MAX_ATTACK_RADIUS * 0.9; |
|
float flMinRange = HOUNDEYE_MIN_ATTACK_RADIUS; |
|
Vector posLos; |
|
bool foundLos = false; |
|
|
|
if (GetEnemy() != NULL) |
|
{ |
|
foundLos = GetTacticalServices()->FindLos(GetEnemyLKP(),GetEnemy()->EyePosition(), flMinRange, flMaxRange, 0.0, &posLos); |
|
} |
|
else |
|
{ |
|
TaskFail(FAIL_NO_TARGET); |
|
return; |
|
} |
|
|
|
if (foundLos) |
|
{ |
|
GetNavigator()->SetGoal( AI_NavGoal_t( posLos, ACT_RUN, AIN_HULL_TOLERANCE ) ); |
|
} |
|
else |
|
{ |
|
TaskFail(FAIL_NO_SHOOT); |
|
} |
|
break; |
|
} |
|
|
|
case TASK_HOUND_FALL_ASLEEP: |
|
{ |
|
m_fAsleep = true; // signal that hound is lying down (must stand again before doing anything else!) |
|
TaskComplete( true ); |
|
break; |
|
} |
|
case TASK_HOUND_WAKE_UP: |
|
{ |
|
m_fAsleep = false; // signal that hound is standing again |
|
TaskComplete( true ); |
|
break; |
|
} |
|
case TASK_HOUND_OPEN_EYE: |
|
{ |
|
m_fDontBlink = false; // turn blinking back on and that code will automatically open the eye |
|
TaskComplete( true ); |
|
break; |
|
} |
|
case TASK_HOUND_CLOSE_EYE: |
|
{ |
|
//<<TEMP>> pev->skin = 0; |
|
m_fDontBlink = true; // tell blink code to leave the eye alone. |
|
break; |
|
} |
|
case TASK_HOUND_THREAT_DISPLAY: |
|
{ |
|
SetIdealActivity( ACT_IDLE_ANGRY ); |
|
break; |
|
} |
|
case TASK_HOUND_HOP_BACK: |
|
{ |
|
SetIdealActivity( ACT_LEAP ); |
|
break; |
|
} |
|
case TASK_RANGE_ATTACK1: |
|
{ |
|
SetIdealActivity( ACT_RANGE_ATTACK1 ); |
|
break; |
|
} |
|
default: |
|
{ |
|
BaseClass::StartTask(pTask); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
//========================================================= |
|
// RunTask |
|
//========================================================= |
|
void CNPC_Houndeye::RunTask( const Task_t *pTask ) |
|
{ |
|
switch ( pTask->iTask ) |
|
{ |
|
case TASK_HOUND_THREAT_DISPLAY: |
|
{ |
|
GetMotor()->SetIdealYawToTargetAndUpdate( GetEnemyLKP(), AI_KEEP_YAW_SPEED ); |
|
|
|
if ( IsActivityFinished() ) |
|
{ |
|
TaskComplete(); |
|
} |
|
|
|
break; |
|
} |
|
case TASK_HOUND_CLOSE_EYE: |
|
{ |
|
/*//<<TEMP>> |
|
if ( pev->skin < HOUNDEYE_EYE_FRAMES - 1 ) |
|
{ |
|
pev->skin++; |
|
} |
|
*/ |
|
break; |
|
} |
|
case TASK_HOUND_HOP_BACK: |
|
{ |
|
if ( IsActivityFinished() ) |
|
{ |
|
TaskComplete(); |
|
} |
|
break; |
|
} |
|
default: |
|
{ |
|
BaseClass::RunTask(pTask); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
void CNPC_Houndeye::PrescheduleThink ( void ) |
|
{ |
|
BaseClass::PrescheduleThink(); |
|
|
|
// if the hound is mad and is running, make hunt noises. |
|
if ( m_NPCState == NPC_STATE_COMBAT && ( GetActivity() == ACT_RUN ) && random->RandomFloat( 0, 1 ) < 0.2 ) |
|
{ |
|
WarnSound(); |
|
} |
|
|
|
// at random, initiate a blink if not already blinking or sleeping |
|
if ( !m_fDontBlink ) |
|
{ |
|
/*//<<TEMP>>//<<TEMP>> |
|
if ( ( pev->skin == 0 ) && random->RandomInt(0,0x7F) == 0 ) |
|
{// start blinking! |
|
pev->skin = HOUNDEYE_EYE_FRAMES - 1; |
|
} |
|
else if ( pev->skin != 0 ) |
|
{// already blinking |
|
pev->skin--; |
|
} |
|
*/ |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Override base class activiites |
|
// Input : |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
Activity CNPC_Houndeye::NPC_TranslateActivity( Activity eNewActivity ) |
|
{ |
|
if ( eNewActivity == ACT_IDLE && m_NPCState == NPC_STATE_COMBAT ) |
|
{ |
|
return ACT_IDLE_ANGRY; |
|
} |
|
return eNewActivity; |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
int CNPC_Houndeye::TranslateSchedule( int scheduleType ) |
|
{ |
|
switch( scheduleType ) |
|
{ |
|
case SCHED_IDLE_STAND: |
|
{ |
|
return BaseClass::TranslateSchedule( scheduleType ); |
|
} |
|
case SCHED_RANGE_ATTACK1: |
|
{ |
|
return SCHED_HOUND_RANGE_ATTACK1; |
|
} |
|
case SCHED_CHASE_ENEMY_FAILED: |
|
{ |
|
return SCHED_COMBAT_FACE; |
|
} |
|
default: |
|
{ |
|
return BaseClass::TranslateSchedule ( scheduleType ); |
|
} |
|
} |
|
} |
|
|
|
//------------------------------------------------------------------------------ |
|
// Purpose : Is anyone in the squad currently attacking |
|
// Input : |
|
// Output : |
|
//------------------------------------------------------------------------------ |
|
bool CNPC_Houndeye::IsAnyoneInSquadAttacking( void ) |
|
{ |
|
if (!m_pSquad) |
|
{ |
|
return false; |
|
} |
|
|
|
AISquadIter_t iter; |
|
for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) ) |
|
{ |
|
if (pSquadMember->IsCurSchedule(SCHED_HOUND_RANGE_ATTACK1)) |
|
{ |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
//========================================================= |
|
// SelectSchedule |
|
//========================================================= |
|
int CNPC_Houndeye::SelectSchedule( void ) |
|
{ |
|
switch ( m_NPCState ) |
|
{ |
|
case NPC_STATE_IDLE: |
|
case NPC_STATE_ALERT: |
|
{ |
|
if ( HasCondition(COND_LIGHT_DAMAGE) || |
|
HasCondition(COND_HEAVY_DAMAGE) ) |
|
{ |
|
return SCHED_TAKE_COVER_FROM_ORIGIN; |
|
} |
|
break; |
|
} |
|
case NPC_STATE_COMBAT: |
|
{ |
|
// dead enemy |
|
|
|
if ( HasCondition( COND_ENEMY_DEAD ) ) |
|
{ |
|
// call base class, all code to handle dead enemies is centralized there. |
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
// If a group attack was requested attack even if attack conditions not met |
|
if ( HasCondition( COND_HOUND_GROUP_ATTACK )) |
|
{ |
|
// Check that I'm not standing in another hound eye |
|
// before attacking |
|
trace_t tr; |
|
AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,1), |
|
GetHullMins(), GetHullMaxs(), |
|
MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); |
|
if (!tr.startsolid) |
|
{ |
|
return SCHED_HOUND_GROUP_ATTACK; |
|
} |
|
|
|
// Otherwise attack as soon as I can |
|
else |
|
{ |
|
m_flNextAttack = gpGlobals->curtime; |
|
SCHED_HOUND_ATTACK_STRAFE; |
|
} |
|
} |
|
|
|
// If a group retread was requested |
|
if ( HasCondition( COND_HOUND_GROUP_RETREAT )) |
|
{ |
|
return SCHED_HOUND_GROUP_RETREAT; |
|
} |
|
|
|
if ( HasCondition( COND_LIGHT_DAMAGE ) | |
|
HasCondition( COND_HEAVY_DAMAGE ) ) |
|
{ |
|
if ( random->RandomFloat( 0 , 1 ) <= 0.4 ) |
|
{ |
|
trace_t tr; |
|
Vector forward; |
|
AngleVectors( GetAbsAngles(), &forward ); |
|
AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + forward * -128, |
|
GetHullMins(), GetHullMaxs(), |
|
MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr ); |
|
|
|
if ( tr.fraction == 1.0 ) |
|
{ |
|
// it's clear behind, so the hound will jump |
|
return SCHED_HOUND_HOP_RETREAT; |
|
} |
|
} |
|
|
|
return SCHED_TAKE_COVER_FROM_ENEMY; |
|
} |
|
|
|
// If a group rally was requested |
|
if ( HasCondition( COND_HOUND_GROUP_RALLEY )) |
|
{ |
|
return SCHED_HOUND_GROUP_RALLEY; |
|
} |
|
|
|
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) ) |
|
{ |
|
if (m_pSquad && random->RandomInt(0,4) == 0) |
|
{ |
|
if (!IsAnyoneInSquadAttacking()) |
|
{ |
|
EmitSound( "NPC_Houndeye.GroupAttack" ); |
|
|
|
m_flSoundWaitTime = gpGlobals->curtime + 1.0; |
|
|
|
m_pSquad->BroadcastInteraction( g_interactionHoundeyeGroupAttack, NULL, this ); |
|
return SCHED_HOUND_GROUP_ATTACK; |
|
} |
|
} |
|
//<<TEMP>>comment |
|
SetCollisionGroup( COLLISION_GROUP_NONE ); |
|
return SCHED_RANGE_ATTACK1; |
|
} |
|
else |
|
{ |
|
if (m_pSquad && random->RandomInt(0,5) == 0) |
|
{ |
|
if (!IsAnyoneInSquadAttacking()) |
|
{ |
|
EmitSound( "NPC_Houndeye.GroupFollow" ); |
|
|
|
m_flSoundWaitTime = gpGlobals->curtime + 1.0; |
|
|
|
m_pSquad->BroadcastInteraction( g_interactionHoundeyeGroupRalley, NULL, this ); |
|
return SCHED_HOUND_ATTACK_STRAFE; |
|
} |
|
} |
|
return SCHED_HOUND_ATTACK_STRAFE; |
|
} |
|
break; |
|
} |
|
} |
|
|
|
return BaseClass::SelectSchedule(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This is a generic function (to be implemented by sub-classes) to |
|
// handle specific interactions between different types of characters |
|
// (For example the barnacle grabbing an NPC) |
|
// Input : Constant for the type of interaction |
|
// Output : true - if sub-class has a response for the interaction |
|
// false - if sub-class has no response |
|
//----------------------------------------------------------------------------- |
|
bool CNPC_Houndeye::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt) |
|
{ |
|
if (interactionType == g_interactionHoundeyeGroupAttack) |
|
{ |
|
SetCondition(COND_HOUND_GROUP_ATTACK); |
|
return true; |
|
} |
|
else if (interactionType == g_interactionHoundeyeGroupRetreat) |
|
{ |
|
SetCondition(COND_HOUND_GROUP_RETREAT); |
|
return true; |
|
} |
|
else if (interactionType == g_interactionHoundeyeGroupRalley) |
|
{ |
|
SetCondition(COND_HOUND_GROUP_RALLEY); |
|
SetTarget(sourceEnt); |
|
m_bLoopClockwise = false; |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
// Schedules |
|
// |
|
//----------------------------------------------------------------------------- |
|
|
|
//========================================================= |
|
// SCHED_HOUND_ATTACK_STRAFE |
|
// |
|
// Run a cirle around my enemy |
|
//========================================================= |
|
AI_DEFINE_SCHEDULE |
|
( |
|
SCHED_HOUND_ATTACK_STRAFE , |
|
|
|
" Tasks " |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HOUND_ATTACK_STRAFE_REVERSE" |
|
" TASK_HOUND_GET_PATH_TO_CIRCLE 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
"" |
|
" Interrupts " |
|
" COND_WEAPON_SIGHT_OCCLUDED" |
|
" COND_NEW_ENEMY" |
|
" COND_ENEMY_DEAD" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_CAN_RANGE_ATTACK1" |
|
" COND_HOUND_GROUP_ATTACK" |
|
" COND_HOUND_GROUP_RETREAT" |
|
); |
|
|
|
//========================================================= |
|
// SCHED_HOUND_ATTACK_STRAFE_REVERSE |
|
// |
|
// Run a cirle around my enemy |
|
//========================================================= |
|
AI_DEFINE_SCHEDULE |
|
( |
|
SCHED_HOUND_ATTACK_STRAFE_REVERSE , |
|
|
|
" Tasks " |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HOUND_CHASE_ENEMY" |
|
" TASK_HOUND_REVERSE_STRAFE_DIR 0" |
|
" TASK_HOUND_GET_PATH_TO_CIRCLE 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
"" |
|
" Interrupts " |
|
" COND_WEAPON_SIGHT_OCCLUDED" |
|
" COND_NEW_ENEMY" |
|
" COND_ENEMY_DEAD" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_CAN_RANGE_ATTACK1" |
|
" COND_HOUND_GROUP_ATTACK" |
|
" COND_HOUND_GROUP_RETREAT" |
|
); |
|
|
|
//======================================================== |
|
// SCHED_HOUND_CHASE_ENEMY |
|
//========================================================= |
|
AI_DEFINE_SCHEDULE |
|
( |
|
SCHED_HOUND_CHASE_ENEMY, |
|
|
|
" Tasks" |
|
" TASK_SET_TOLERANCE_DISTANCE 30 " |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED" |
|
" TASK_GET_PATH_TO_ENEMY 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
"" |
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
" COND_ENEMY_DEAD" |
|
" COND_CAN_RANGE_ATTACK1" |
|
" COND_HOUND_GROUP_ATTACK" |
|
" COND_HOUND_GROUP_RETREAT" |
|
); |
|
|
|
//========================================================= |
|
// SCHED_HOUND_GROUP_ATTACK |
|
// |
|
// Face enemy, pause, then attack |
|
//========================================================= |
|
AI_DEFINE_SCHEDULE |
|
( |
|
SCHED_HOUND_GROUP_ATTACK , |
|
|
|
" Tasks " |
|
" TASK_STOP_MOVING 0" |
|
" TASK_FACE_ENEMY 0" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE_ANGRY" |
|
" TASK_SPEAK_SENTENCE 0" |
|
" TASK_WAIT 1" |
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_HOUND_RANGE_ATTACK1" |
|
"" |
|
" Interrupts " |
|
" COND_NEW_ENEMY" |
|
" COND_ENEMY_DEAD" |
|
" COND_HEAVY_DAMAGE" |
|
); |
|
|
|
//========================================================= |
|
// > SCHED_HOUND_GROUP_RETREAT |
|
// |
|
// Take cover from enemy! |
|
//========================================================= |
|
AI_DEFINE_SCHEDULE |
|
( |
|
SCHED_HOUND_GROUP_RETREAT, |
|
|
|
" Tasks" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HOUND_ATTACK_STRAFE" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_WAIT 0.2" |
|
" TASK_SET_TOLERANCE_DISTANCE 24" |
|
" TASK_FIND_COVER_FROM_ENEMY 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
" TASK_REMEMBER MEMORY:INCOVER" |
|
" TASK_FACE_ENEMY 0" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover |
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_HOUND_COVER_WAIT" |
|
"" |
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
); |
|
|
|
//========================================================= |
|
// > SCHED_HOUND_GROUP_RALLEY |
|
// |
|
// Run to rally hound! |
|
//========================================================= |
|
AI_DEFINE_SCHEDULE |
|
( |
|
SCHED_HOUND_GROUP_RALLEY, |
|
|
|
" Tasks" |
|
" TASK_SET_TOLERANCE_DISTANCE 30" |
|
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HOUND_ATTACK_STRAFE" |
|
" TASK_GET_PATH_TO_TARGET 0" |
|
" TASK_RUN_PATH 0" |
|
" TASK_WAIT_FOR_MOVEMENT 0" |
|
"" |
|
" Interrupts" |
|
" COND_NEW_ENEMY" |
|
" COND_ENEMY_DEAD" |
|
" COND_HEAVY_DAMAGE" |
|
" COND_HOUND_GROUP_ATTACK" |
|
" COND_HOUND_GROUP_RETREAT" |
|
); |
|
|
|
//========================================================= |
|
// > SCHED_HOUND_COVER_WAIT |
|
// |
|
// Wait in cover till enemy see's me or I take damage |
|
//========================================================= |
|
AI_DEFINE_SCHEDULE |
|
( |
|
SCHED_HOUND_COVER_WAIT, |
|
|
|
" Tasks" |
|
" TASK_WAIT 2" |
|
"" |
|
" Interrupts" |
|
" COND_SEE_ENEMY" |
|
" COND_LIGHT_DAMAGE" |
|
); |
|
|
|
//========================================================= |
|
// > SCHED_HOUND_RANGE_ATTACK1 |
|
//========================================================= |
|
AI_DEFINE_SCHEDULE |
|
( |
|
SCHED_HOUND_RANGE_ATTACK1, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE_ANGRY" |
|
" TASK_FACE_IDEAL 0" |
|
" TASK_RANGE_ATTACK1 0" |
|
"" |
|
" Interrupts" |
|
//" COND_LIGHT_DAMAGE" // don't interupt on small damage |
|
" COND_HEAVY_DAMAGE" |
|
); |
|
|
|
//========================================================= |
|
// > SCHED_HOUND_HOP_RETREAT |
|
//========================================================= |
|
AI_DEFINE_SCHEDULE |
|
( |
|
SCHED_HOUND_HOP_RETREAT, |
|
|
|
" Tasks" |
|
" TASK_STOP_MOVING 0" |
|
" TASK_HOUND_HOP_BACK 0" |
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY" |
|
"" |
|
" Interrupts" |
|
);
|
|
|