summaryrefslogtreecommitdiff
path: root/game/server/hl2/npc_houndeye.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/hl2/npc_houndeye.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'game/server/hl2/npc_houndeye.cpp')
-rw-r--r--game/server/hl2/npc_houndeye.cpp1395
1 files changed, 1395 insertions, 0 deletions
diff --git a/game/server/hl2/npc_houndeye.cpp b/game/server/hl2/npc_houndeye.cpp
new file mode 100644
index 0000000..c121d11
--- /dev/null
+++ b/game/server/hl2/npc_houndeye.cpp
@@ -0,0 +1,1395 @@
+//========= 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"
+);