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.
1364 lines
32 KiB
1364 lines
32 KiB
/*** |
|
* |
|
* Copyright (c) 1996-2002, Valve LLC. All rights reserved. |
|
* |
|
* This product contains software technology licensed from Id |
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. |
|
* All Rights Reserved. |
|
* |
|
* This source code contains proprietary and confidential information of |
|
* Valve LLC and its suppliers. Access to this code is restricted to |
|
* persons who have executed a written SDK license with Valve. Any access, |
|
* use or distribution of this code by or to any unlicensed person is illegal. |
|
* |
|
****/ |
|
//========================================================= |
|
// Houndeye - spooky sonic dog. |
|
//========================================================= |
|
|
|
#include "extdll.h" |
|
#include "util.h" |
|
#include "cbase.h" |
|
#include "monsters.h" |
|
#include "schedule.h" |
|
#include "animation.h" |
|
#include "nodes.h" |
|
#include "squadmonster.h" |
|
#include "soundent.h" |
|
#include "game.h" |
|
#include "weapons.h" |
|
|
|
extern CGraph WorldGraph; |
|
|
|
// houndeye does 20 points of damage spread over a sphere 384 units in diameter, and each additional |
|
// squad member increases the BASE damage by 110%, per the spec. |
|
#define HOUNDEYE_MAX_SQUAD_SIZE 4 |
|
#define HOUNDEYE_MAX_ATTACK_RADIUS 384.0f |
|
#define HOUNDEYE_MIN_ATTACK_RADIUS 128 |
|
#define HOUNDEYE_SQUAD_BONUS (float)1.1f |
|
|
|
#define HOUNDEYE_EYE_FRAMES 4 // how many different switchable maps for the eye |
|
|
|
#define HOUNDEYE_SOUND_STARTLE_VOLUME 128 // how loud a sound has to be to badly scare a sleeping houndeye |
|
|
|
//========================================================= |
|
// monster-specific tasks |
|
//========================================================= |
|
enum |
|
{ |
|
TASK_HOUND_CLOSE_EYE = LAST_COMMON_TASK + 1, |
|
TASK_HOUND_OPEN_EYE, |
|
TASK_HOUND_THREAT_DISPLAY, |
|
TASK_HOUND_FALL_ASLEEP, |
|
TASK_HOUND_WAKE_UP, |
|
TASK_HOUND_HOP_BACK |
|
}; |
|
|
|
//========================================================= |
|
// monster-specific schedule types |
|
//========================================================= |
|
enum |
|
{ |
|
SCHED_HOUND_AGITATED = LAST_COMMON_SCHEDULE + 1, |
|
SCHED_HOUND_HOP_RETREAT, |
|
SCHED_HOUND_FAIL |
|
}; |
|
|
|
//========================================================= |
|
// 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 |
|
|
|
class CHoundeye : public CSquadMonster |
|
{ |
|
public: |
|
void Spawn( void ); |
|
void Precache( void ); |
|
int Classify( void ); |
|
void HandleAnimEvent( MonsterEvent_t *pEvent ); |
|
void SetYawSpeed( void ); |
|
void WarmUpSound( void ); |
|
void AlertSound( void ); |
|
void DeathSound( void ); |
|
void WarnSound( void ); |
|
void PainSound( void ); |
|
void IdleSound( void ); |
|
void StartTask( Task_t *pTask ); |
|
void RunTask( Task_t *pTask ); |
|
void SonicAttack( void ); |
|
void PrescheduleThink( void ); |
|
void SetActivity( Activity NewActivity ); |
|
void WriteBeamColor( void ); |
|
BOOL CheckRangeAttack1( float flDot, float flDist ); |
|
BOOL FValidateHintType( short sHint ); |
|
BOOL FCanActiveIdle( void ); |
|
Schedule_t *GetScheduleOfType( int Type ); |
|
Schedule_t *GetSchedule( void ); |
|
|
|
int Save( CSave &save ); |
|
int Restore( CRestore &restore ); |
|
|
|
CUSTOM_SCHEDULES |
|
static TYPEDESCRIPTION m_SaveData[]; |
|
|
|
int m_iSpriteTexture; |
|
BOOL m_fAsleep;// some houndeyes sleep in idle mode if this is set, the houndeye is lying down |
|
BOOL m_fDontBlink;// don't try to open/close eye if this bit is set! |
|
Vector m_vecPackCenter; // the center of the pack. The leader maintains this by averaging the origins of all pack members. |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( monster_houndeye, CHoundeye ) |
|
|
|
TYPEDESCRIPTION CHoundeye::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CHoundeye, m_iSpriteTexture, FIELD_INTEGER ), |
|
DEFINE_FIELD( CHoundeye, m_fAsleep, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( CHoundeye, m_fDontBlink, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( CHoundeye, m_vecPackCenter, FIELD_POSITION_VECTOR ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CHoundeye, CSquadMonster ) |
|
|
|
//========================================================= |
|
// Classify - indicates this monster's place in the |
|
// relationship table. |
|
//========================================================= |
|
int CHoundeye::Classify( void ) |
|
{ |
|
return CLASS_ALIEN_MONSTER; |
|
} |
|
|
|
//========================================================= |
|
// FValidateHintType |
|
//========================================================= |
|
BOOL CHoundeye::FValidateHintType( short sHint ) |
|
{ |
|
size_t i; |
|
|
|
static short sHoundHints[] = |
|
{ |
|
HINT_WORLD_MACHINERY, |
|
HINT_WORLD_BLINKING_LIGHT, |
|
HINT_WORLD_HUMAN_BLOOD, |
|
HINT_WORLD_ALIEN_BLOOD, |
|
}; |
|
|
|
for( i = 0; i < ARRAYSIZE( sHoundHints ); i++ ) |
|
{ |
|
if( sHoundHints[i] == sHint ) |
|
{ |
|
return TRUE; |
|
} |
|
} |
|
|
|
ALERT( at_aiconsole, "Couldn't validate hint type\n" ); |
|
return FALSE; |
|
} |
|
|
|
//========================================================= |
|
// FCanActiveIdle |
|
//========================================================= |
|
BOOL CHoundeye::FCanActiveIdle( void ) |
|
{ |
|
if( InSquad() ) |
|
{ |
|
CSquadMonster *pSquadLeader = MySquadLeader(); |
|
|
|
for( int i = 0; i < MAX_SQUAD_MEMBERS; i++ ) |
|
{ |
|
CSquadMonster *pMember = pSquadLeader->MySquadMember( i ); |
|
|
|
if( pMember != NULL && pMember != this && pMember->m_iHintNode != NO_NODE ) |
|
{ |
|
// someone else in the group is active idling right now! |
|
return FALSE; |
|
} |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
return TRUE; |
|
} |
|
|
|
//========================================================= |
|
// CheckRangeAttack1 - overridden for houndeyes so that they |
|
// try to get within half of their max attack radius before |
|
// attacking, so as to increase their chances of doing damage. |
|
//========================================================= |
|
BOOL CHoundeye::CheckRangeAttack1( float flDot, float flDist ) |
|
{ |
|
float flRadius; |
|
|
|
if( FBitSet( pev->spawnflags, SF_SQUADMONSTER_NOZAP ) ) |
|
flRadius = HOUNDEYE_MIN_ATTACK_RADIUS; |
|
else |
|
flRadius = HOUNDEYE_MAX_ATTACK_RADIUS; |
|
|
|
if( flDist <= ( flRadius * 0.5f ) && flDot >= 0.3f ) |
|
{ |
|
return TRUE; |
|
} |
|
return FALSE; |
|
} |
|
|
|
//========================================================= |
|
// SetYawSpeed - allows each sequence to have a different |
|
// turn rate associated with it. |
|
//========================================================= |
|
void CHoundeye::SetYawSpeed( void ) |
|
{ |
|
int ys; |
|
|
|
ys = 90; |
|
|
|
switch( m_Activity ) |
|
{ |
|
case ACT_CROUCHIDLE://sleeping! |
|
ys = 0; |
|
break; |
|
case ACT_IDLE: |
|
ys = 60; |
|
break; |
|
case ACT_WALK: |
|
case ACT_RUN: |
|
case ACT_TURN_LEFT: |
|
case ACT_TURN_RIGHT: |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
pev->yaw_speed = ys; |
|
} |
|
|
|
//========================================================= |
|
// SetActivity |
|
//========================================================= |
|
void CHoundeye::SetActivity( Activity NewActivity ) |
|
{ |
|
int iSequence; |
|
|
|
if( NewActivity == m_Activity ) |
|
return; |
|
|
|
if( m_MonsterState == MONSTERSTATE_COMBAT && NewActivity == ACT_IDLE && RANDOM_LONG( 0, 1 ) ) |
|
{ |
|
// play pissed idle. |
|
iSequence = LookupSequence( "madidle" ); |
|
|
|
m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present |
|
|
|
// In case someone calls this with something other than the ideal activity |
|
m_IdealActivity = m_Activity; |
|
|
|
// Set to the desired anim, or default anim if the desired is not present |
|
if( iSequence > ACTIVITY_NOT_AVAILABLE ) |
|
{ |
|
pev->sequence = iSequence; // Set to the reset anim (if it's there) |
|
pev->frame = 0; // FIX: frame counter shouldn't be reset when its the same activity as before |
|
ResetSequenceInfo(); |
|
SetYawSpeed(); |
|
} |
|
} |
|
else |
|
{ |
|
CSquadMonster::SetActivity( NewActivity ); |
|
} |
|
} |
|
|
|
//========================================================= |
|
// HandleAnimEvent - catches the monster-specific messages |
|
// that occur when tagged animation frames are played. |
|
//========================================================= |
|
void CHoundeye::HandleAnimEvent( MonsterEvent_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 = g_psv_gravity->value; |
|
|
|
pev->flags &= ~FL_ONGROUND; |
|
|
|
pev->velocity = gpGlobals->v_forward * -200.0f; |
|
pev->velocity.z += ( 0.6f * flGravity ) * 0.5f; |
|
break; |
|
} |
|
case HOUND_AE_THUMP: |
|
// emit the shockwaves |
|
SonicAttack(); |
|
break; |
|
case HOUND_AE_ANGERSOUND1: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM ); |
|
break; |
|
case HOUND_AE_ANGERSOUND2: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_pain1.wav", 1, ATTN_NORM ); |
|
break; |
|
case HOUND_AE_CLOSE_EYE: |
|
if( !m_fDontBlink ) |
|
{ |
|
pev->skin = HOUNDEYE_EYE_FRAMES - 1; |
|
} |
|
break; |
|
default: |
|
CSquadMonster::HandleAnimEvent( pEvent ); |
|
break; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// Spawn |
|
//========================================================= |
|
void CHoundeye::Spawn() |
|
{ |
|
Precache(); |
|
|
|
SET_MODEL( ENT( pev ), "models/houndeye.mdl" ); |
|
UTIL_SetSize( pev, Vector( -16, -16, 0 ), Vector( 16, 16, 36 ) ); |
|
|
|
pev->solid = SOLID_SLIDEBOX; |
|
pev->movetype = MOVETYPE_STEP; |
|
m_bloodColor = BLOOD_COLOR_RED; |
|
pev->effects = 0; |
|
pev->health = gSkillData.houndeyeHealth; |
|
pev->yaw_speed = 5;//!!! should we put this in the monster's changeanim function since turn rates may vary with state/anim? |
|
m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) |
|
m_MonsterState = MONSTERSTATE_NONE; |
|
m_fAsleep = FALSE; // everyone spawns awake |
|
m_fDontBlink = FALSE; |
|
m_afCapability |= bits_CAP_SQUAD; |
|
|
|
MonsterInit(); |
|
} |
|
|
|
//========================================================= |
|
// Precache - precaches all resources this monster needs |
|
//========================================================= |
|
void CHoundeye::Precache() |
|
{ |
|
PRECACHE_MODEL( "models/houndeye.mdl" ); |
|
|
|
PRECACHE_SOUND( "houndeye/rex_bite1.wav" ); |
|
PRECACHE_SOUND( "houndeye/rex_bite2.wav" ); |
|
PRECACHE_SOUND( "houndeye/rex_bite3.wav" ); |
|
|
|
PRECACHE_SOUND( "houndeye/he_alert1.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_alert2.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_alert3.wav" ); |
|
|
|
PRECACHE_SOUND( "houndeye/he_die1.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_die2.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_die3.wav" ); |
|
|
|
PRECACHE_SOUND( "houndeye/he_idle1.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_idle2.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_idle3.wav" ); |
|
|
|
PRECACHE_SOUND( "houndeye/he_hunt1.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_hunt2.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_hunt3.wav" ); |
|
|
|
PRECACHE_SOUND( "houndeye/he_pain1.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_pain3.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_pain4.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_pain5.wav" ); |
|
|
|
PRECACHE_SOUND( "houndeye/he_attack1.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_attack2.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_attack3.wav" ); |
|
|
|
if( !FBitSet( pev->spawnflags, SF_SQUADMONSTER_NOZAP ) ) |
|
{ |
|
PRECACHE_SOUND( "houndeye/he_blast1.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_blast2.wav" ); |
|
PRECACHE_SOUND( "houndeye/he_blast3.wav" ); |
|
|
|
m_iSpriteTexture = PRECACHE_MODEL( "sprites/shockwave.spr" ); |
|
} |
|
} |
|
|
|
//========================================================= |
|
// IdleSound |
|
//========================================================= |
|
void CHoundeye::IdleSound( void ) |
|
{ |
|
switch( RANDOM_LONG( 0, 2 ) ) |
|
{ |
|
case 0: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_idle1.wav", 1, ATTN_NORM ); |
|
break; |
|
case 1: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_idle2.wav", 1, ATTN_NORM ); |
|
break; |
|
case 2: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_idle3.wav", 1, ATTN_NORM ); |
|
break; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// IdleSound |
|
//========================================================= |
|
void CHoundeye::WarmUpSound( void ) |
|
{ |
|
const char *pszSound; |
|
|
|
if( FBitSet( pev->spawnflags, SF_SQUADMONSTER_NOZAP ) ) |
|
{ |
|
Vector vecMouthPos, vecMouthAng; |
|
CBaseEntity *pHurt = 0; |
|
pHurt = CheckTraceHullAttack( 80, gSkillData.houndeyeDmgBlast * 0.4f, DMG_SLASH ); |
|
|
|
if( pHurt ) |
|
{ |
|
if( pHurt->pev->flags & ( FL_CLIENT | FL_MONSTER ) ) |
|
{ |
|
pHurt->pev->punchangle.x = 8; |
|
pHurt->pev->punchangle.y = RANDOM_LONG( -8, 8 ); |
|
pHurt->pev->punchangle.z = RANDOM_LONG( -8, 8 ); |
|
} |
|
|
|
if( pHurt->IsPlayer() ) |
|
{ |
|
pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100; |
|
pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 50; |
|
} |
|
GetAttachment( 0, vecMouthPos, vecMouthAng ); |
|
SpawnBlood( vecMouthPos, pHurt->BloodColor(), 25 ); |
|
} |
|
|
|
switch( RANDOM_LONG( 0, 2 ) ) |
|
{ |
|
case 0: |
|
pszSound = "houndeye/rex_bite1.wav"; |
|
break; |
|
case 1: |
|
pszSound = "houndeye/rex_bite2.wav"; |
|
break; |
|
case 2: |
|
pszSound = "houndeye/rex_bite3.wav"; |
|
break; |
|
} |
|
} |
|
else |
|
{ |
|
switch( RANDOM_LONG( 0, 2 ) ) |
|
{ |
|
case 0: |
|
pszSound = "houndeye/he_attack1.wav"; |
|
break; |
|
case 1: |
|
pszSound = "houndeye/he_attack3.wav"; |
|
break; |
|
case 2: |
|
pszSound = "houndeye/he_attack2.wav"; |
|
break; |
|
} |
|
} |
|
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, pszSound, 0.7, ATTN_NORM ); |
|
} |
|
|
|
//========================================================= |
|
// WarnSound |
|
//========================================================= |
|
void CHoundeye::WarnSound( void ) |
|
{ |
|
switch( RANDOM_LONG( 0, 2 ) ) |
|
{ |
|
case 0: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_hunt1.wav", 1, ATTN_NORM ); |
|
break; |
|
case 1: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_hunt2.wav", 1, ATTN_NORM ); |
|
break; |
|
case 2: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_hunt3.wav", 1, ATTN_NORM ); |
|
break; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// AlertSound |
|
//========================================================= |
|
void CHoundeye::AlertSound( void ) |
|
{ |
|
if( InSquad() && !IsLeader() ) |
|
{ |
|
return; // only leader makes ALERT sound. |
|
} |
|
|
|
switch( RANDOM_LONG( 0, 2 ) ) |
|
{ |
|
case 0: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_alert1.wav", 1, ATTN_NORM ); |
|
break; |
|
case 1: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_alert2.wav", 1, ATTN_NORM ); |
|
break; |
|
case 2: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_alert3.wav", 1, ATTN_NORM ); |
|
break; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// DeathSound |
|
//========================================================= |
|
void CHoundeye::DeathSound( void ) |
|
{ |
|
switch( RANDOM_LONG( 0, 2 ) ) |
|
{ |
|
case 0: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_die1.wav", 1, ATTN_NORM ); |
|
break; |
|
case 1: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_die2.wav", 1, ATTN_NORM ); |
|
break; |
|
case 2: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_die3.wav", 1, ATTN_NORM ); |
|
break; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// PainSound |
|
//========================================================= |
|
void CHoundeye::PainSound( void ) |
|
{ |
|
switch( RANDOM_LONG( 0, 2 ) ) |
|
{ |
|
case 0: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM ); |
|
break; |
|
case 1: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_pain4.wav", 1, ATTN_NORM ); |
|
break; |
|
case 2: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "houndeye/he_pain5.wav", 1, ATTN_NORM ); |
|
break; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// WriteBeamColor - writes a color vector to the network |
|
// based on the size of the group. |
|
//========================================================= |
|
void CHoundeye::WriteBeamColor( void ) |
|
{ |
|
BYTE bRed, bGreen, bBlue; |
|
|
|
if( InSquad() ) |
|
{ |
|
switch( SquadCount() ) |
|
{ |
|
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: |
|
ALERT( at_aiconsole, "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 ); |
|
} |
|
|
|
//========================================================= |
|
// SonicAttack |
|
//========================================================= |
|
void CHoundeye::SonicAttack( void ) |
|
{ |
|
float flAdjustedDamage; |
|
float flDist; |
|
|
|
if( FBitSet( pev->spawnflags, SF_SQUADMONSTER_NOZAP ) ) |
|
{ |
|
WarnSound(); |
|
return; |
|
} |
|
|
|
switch( RANDOM_LONG( 0, 2 ) ) |
|
{ |
|
case 0: |
|
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "houndeye/he_blast1.wav", 1, ATTN_NORM ); |
|
break; |
|
case 1: |
|
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "houndeye/he_blast2.wav", 1, ATTN_NORM ); |
|
break; |
|
case 2: |
|
EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "houndeye/he_blast3.wav", 1, ATTN_NORM ); |
|
break; |
|
} |
|
|
|
// blast circles |
|
MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); |
|
WRITE_BYTE( TE_BEAMCYLINDER ); |
|
WRITE_COORD( pev->origin.x ); |
|
WRITE_COORD( pev->origin.y ); |
|
WRITE_COORD( pev->origin.z + 16.0f ); |
|
WRITE_COORD( pev->origin.x ); |
|
WRITE_COORD( pev->origin.y ); |
|
WRITE_COORD( pev->origin.z + 16.0f + HOUNDEYE_MAX_ATTACK_RADIUS / 0.2f ); // reach damage radius over .3 seconds |
|
WRITE_SHORT( m_iSpriteTexture ); |
|
WRITE_BYTE( 0 ); // startframe |
|
WRITE_BYTE( 0 ); // framerate |
|
WRITE_BYTE( 2 ); // life |
|
WRITE_BYTE( 16 ); // width |
|
WRITE_BYTE( 0 ); // noise |
|
|
|
WriteBeamColor(); |
|
|
|
WRITE_BYTE( 255 ); //brightness |
|
WRITE_BYTE( 0 ); // speed |
|
MESSAGE_END(); |
|
|
|
MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); |
|
WRITE_BYTE( TE_BEAMCYLINDER ); |
|
WRITE_COORD( pev->origin.x ); |
|
WRITE_COORD( pev->origin.y ); |
|
WRITE_COORD( pev->origin.z + 16.0f ); |
|
WRITE_COORD( pev->origin.x ); |
|
WRITE_COORD( pev->origin.y ); |
|
WRITE_COORD( pev->origin.z + 16.0f + ( HOUNDEYE_MAX_ATTACK_RADIUS / 2.0f ) / 0.2f ); // reach damage radius over .3 seconds |
|
WRITE_SHORT( m_iSpriteTexture ); |
|
WRITE_BYTE( 0 ); // startframe |
|
WRITE_BYTE( 0 ); // framerate |
|
WRITE_BYTE( 2 ); // life |
|
WRITE_BYTE( 16 ); // width |
|
WRITE_BYTE( 0 ); // noise |
|
|
|
WriteBeamColor(); |
|
|
|
WRITE_BYTE( 255 ); //brightness |
|
WRITE_BYTE( 0 ); // speed |
|
MESSAGE_END(); |
|
|
|
CBaseEntity *pEntity = NULL; |
|
// iterate on all entities in the vicinity. |
|
while( ( pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, HOUNDEYE_MAX_ATTACK_RADIUS ) ) != NULL ) |
|
{ |
|
if( pEntity->pev->takedamage != DAMAGE_NO ) |
|
{ |
|
if( !FClassnameIs( pEntity->pev, "monster_houndeye" ) ) |
|
{ |
|
// houndeyes don't hurt other houndeyes with their attack |
|
// houndeyes do FULL damage if the ent in question is visible. Half damage otherwise. |
|
// This means that you must get out of the houndeye's attack range entirely to avoid damage. |
|
// Calculate full damage first |
|
|
|
if( SquadCount() > 1 ) |
|
{ |
|
// squad gets attack bonus. |
|
flAdjustedDamage = gSkillData.houndeyeDmgBlast + gSkillData.houndeyeDmgBlast * ( HOUNDEYE_SQUAD_BONUS * ( SquadCount() - 1 ) ); |
|
} |
|
else |
|
{ |
|
// solo |
|
flAdjustedDamage = gSkillData.houndeyeDmgBlast; |
|
} |
|
|
|
flDist = ( pEntity->Center() - pev->origin ).Length(); |
|
|
|
flAdjustedDamage -= ( flDist / HOUNDEYE_MAX_ATTACK_RADIUS ) * flAdjustedDamage; |
|
|
|
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. |
|
flAdjustedDamage *= 0.5f; |
|
} |
|
else if( !FClassnameIs( pEntity->pev, "func_breakable" ) && !FClassnameIs( pEntity->pev, "func_pushable" ) ) |
|
{ |
|
// do not hurt nonclients through walls, but allow damage to be done to breakables |
|
flAdjustedDamage = 0; |
|
} |
|
} |
|
|
|
//ALERT ( at_aiconsole, "Damage: %f\n", flAdjustedDamage ); |
|
|
|
if( flAdjustedDamage > 0 ) |
|
{ |
|
pEntity->TakeDamage( pev, pev, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//========================================================= |
|
// start task |
|
//========================================================= |
|
void CHoundeye::StartTask( Task_t *pTask ) |
|
{ |
|
m_iTaskStatus = TASKSTATUS_RUNNING; |
|
|
|
switch( pTask->iTask ) |
|
{ |
|
case TASK_HOUND_FALL_ASLEEP: |
|
{ |
|
m_fAsleep = TRUE; // signal that hound is lying down (must stand again before doing anything else!) |
|
m_iTaskStatus = TASKSTATUS_COMPLETE; |
|
break; |
|
} |
|
case TASK_HOUND_WAKE_UP: |
|
{ |
|
m_fAsleep = FALSE; // signal that hound is standing again |
|
m_iTaskStatus = TASKSTATUS_COMPLETE; |
|
break; |
|
} |
|
case TASK_HOUND_OPEN_EYE: |
|
{ |
|
m_fDontBlink = FALSE; // turn blinking back on and that code will automatically open the eye |
|
m_iTaskStatus = TASKSTATUS_COMPLETE; |
|
break; |
|
} |
|
case TASK_HOUND_CLOSE_EYE: |
|
{ |
|
pev->skin = 0; |
|
m_fDontBlink = TRUE; // tell blink code to leave the eye alone. |
|
break; |
|
} |
|
case TASK_HOUND_THREAT_DISPLAY: |
|
{ |
|
m_IdealActivity = ACT_IDLE_ANGRY; |
|
break; |
|
} |
|
case TASK_HOUND_HOP_BACK: |
|
{ |
|
m_IdealActivity = ACT_LEAP; |
|
break; |
|
} |
|
case TASK_RANGE_ATTACK1: |
|
{ |
|
m_IdealActivity = ACT_RANGE_ATTACK1; |
|
|
|
/* |
|
if( InSquad() ) |
|
{ |
|
// see if there is a battery to connect to. |
|
CSquadMonster *pSquad = m_pSquadLeader; |
|
|
|
while( pSquad ) |
|
{ |
|
if( pSquad->m_iMySlot == bits_SLOT_HOUND_BATTERY ) |
|
{ |
|
// draw a beam. |
|
MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); |
|
WRITE_BYTE( TE_BEAMENTS ); |
|
WRITE_SHORT( ENTINDEX( this->edict() ) ); |
|
WRITE_SHORT( ENTINDEX( pSquad->edict() ) ); |
|
WRITE_SHORT( m_iSpriteTexture ); |
|
WRITE_BYTE( 0 ); // framestart |
|
WRITE_BYTE( 0 ); // framerate |
|
WRITE_BYTE( 10 ); // life |
|
WRITE_BYTE( 40 ); // width |
|
WRITE_BYTE( 10 ); // noise |
|
WRITE_BYTE( 0 ); // r, g, b |
|
WRITE_BYTE( 50 ); // r, g, b |
|
WRITE_BYTE( 250); // r, g, b |
|
WRITE_BYTE( 255 ); // brightness |
|
WRITE_BYTE( 30 ); // speed |
|
MESSAGE_END(); |
|
break; |
|
} |
|
|
|
pSquad = pSquad->m_pSquadNext; |
|
} |
|
} |
|
*/ |
|
break; |
|
} |
|
case TASK_SPECIAL_ATTACK1: |
|
{ |
|
m_IdealActivity = ACT_SPECIAL_ATTACK1; |
|
break; |
|
} |
|
case TASK_GUARD: |
|
{ |
|
m_IdealActivity = ACT_GUARD; |
|
break; |
|
} |
|
default: |
|
{ |
|
CSquadMonster::StartTask( pTask ); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
//========================================================= |
|
// RunTask |
|
//========================================================= |
|
void CHoundeye::RunTask( Task_t *pTask ) |
|
{ |
|
switch( pTask->iTask ) |
|
{ |
|
case TASK_HOUND_THREAT_DISPLAY: |
|
{ |
|
MakeIdealYaw( m_vecEnemyLKP ); |
|
ChangeYaw( pev->yaw_speed ); |
|
|
|
if( m_fSequenceFinished ) |
|
{ |
|
TaskComplete(); |
|
} |
|
break; |
|
} |
|
case TASK_HOUND_CLOSE_EYE: |
|
{ |
|
if( pev->skin < HOUNDEYE_EYE_FRAMES - 1 ) |
|
{ |
|
pev->skin++; |
|
} |
|
break; |
|
} |
|
case TASK_HOUND_HOP_BACK: |
|
{ |
|
if( m_fSequenceFinished ) |
|
{ |
|
TaskComplete(); |
|
} |
|
break; |
|
} |
|
case TASK_SPECIAL_ATTACK1: |
|
{ |
|
pev->skin = RANDOM_LONG( 0, HOUNDEYE_EYE_FRAMES - 1 ); |
|
|
|
MakeIdealYaw( m_vecEnemyLKP ); |
|
ChangeYaw( pev->yaw_speed ); |
|
|
|
float life; |
|
life = ( ( 255 - pev->frame ) / ( pev->framerate * m_flFrameRate ) ); |
|
if( life < 0.1f ) |
|
life = 0.1f; |
|
|
|
MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); |
|
WRITE_BYTE( TE_IMPLOSION ); |
|
WRITE_COORD( pev->origin.x ); |
|
WRITE_COORD( pev->origin.y ); |
|
WRITE_COORD( pev->origin.z + 16.0f ); |
|
WRITE_BYTE( 50.0f * life + 100.0f ); // radius |
|
WRITE_BYTE( pev->frame / 25.0f ); // count |
|
WRITE_BYTE( life * 10.0f ); // life |
|
MESSAGE_END(); |
|
|
|
if( m_fSequenceFinished ) |
|
{ |
|
SonicAttack(); |
|
TaskComplete(); |
|
} |
|
break; |
|
} |
|
default: |
|
{ |
|
CSquadMonster::RunTask( pTask ); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
//========================================================= |
|
// PrescheduleThink |
|
//========================================================= |
|
void CHoundeye::PrescheduleThink( void ) |
|
{ |
|
// if the hound is mad and is running, make hunt noises. |
|
if( m_MonsterState == MONSTERSTATE_COMBAT && m_Activity == ACT_RUN && RANDOM_FLOAT( 0, 1 ) < 0.2f ) |
|
{ |
|
WarnSound(); |
|
} |
|
|
|
// at random, initiate a blink if not already blinking or sleeping |
|
if( !m_fDontBlink ) |
|
{ |
|
if( ( pev->skin == 0 ) && RANDOM_LONG( 0, 0x7F ) == 0 ) |
|
{ |
|
// start blinking! |
|
pev->skin = HOUNDEYE_EYE_FRAMES - 1; |
|
} |
|
else if( pev->skin != 0 ) |
|
{ |
|
// already blinking |
|
pev->skin--; |
|
} |
|
} |
|
|
|
// if you are the leader, average the origins of each pack member to get an approximate center. |
|
if( IsLeader() ) |
|
{ |
|
CSquadMonster *pSquadMember; |
|
int iSquadCount = 0; |
|
|
|
for( int i = 0; i < MAX_SQUAD_MEMBERS; i++ ) |
|
{ |
|
pSquadMember = MySquadMember( i ); |
|
|
|
if( pSquadMember ) |
|
{ |
|
iSquadCount++; |
|
m_vecPackCenter = m_vecPackCenter + pSquadMember->pev->origin; |
|
} |
|
} |
|
|
|
m_vecPackCenter = m_vecPackCenter / iSquadCount; |
|
} |
|
} |
|
|
|
//========================================================= |
|
// AI Schedules Specific to this monster |
|
//========================================================= |
|
Task_t tlHoundGuardPack[] = |
|
{ |
|
{ TASK_STOP_MOVING, 0.0f }, |
|
{ TASK_GUARD, 0.0f }, |
|
}; |
|
|
|
Schedule_t slHoundGuardPack[] = |
|
{ |
|
{ |
|
tlHoundGuardPack, |
|
ARRAYSIZE ( tlHoundGuardPack ), |
|
bits_COND_SEE_HATE | |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE | |
|
bits_COND_PROVOKED | |
|
bits_COND_HEAR_SOUND, |
|
|
|
bits_SOUND_COMBAT |// sound flags |
|
bits_SOUND_WORLD | |
|
bits_SOUND_MEAT | |
|
bits_SOUND_PLAYER, |
|
"GuardPack" |
|
}, |
|
}; |
|
|
|
// primary range attack |
|
Task_t tlHoundYell1[] = |
|
{ |
|
{ TASK_STOP_MOVING, (float)0 }, |
|
{ TASK_FACE_IDEAL, (float)0 }, |
|
{ TASK_RANGE_ATTACK1, (float)0 }, |
|
{ TASK_SET_SCHEDULE, (float)SCHED_HOUND_AGITATED }, |
|
}; |
|
|
|
Task_t tlHoundYell2[] = |
|
{ |
|
{ TASK_STOP_MOVING, (float)0 }, |
|
{ TASK_FACE_IDEAL, (float)0 }, |
|
{ TASK_RANGE_ATTACK1, (float)0 }, |
|
}; |
|
|
|
Schedule_t slHoundRangeAttack[] = |
|
{ |
|
{ |
|
tlHoundYell1, |
|
ARRAYSIZE ( tlHoundYell1 ), |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE, |
|
0, |
|
"HoundRangeAttack1" |
|
}, |
|
{ |
|
tlHoundYell2, |
|
ARRAYSIZE ( tlHoundYell2 ), |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE, |
|
0, |
|
"HoundRangeAttack2" |
|
}, |
|
}; |
|
|
|
// lie down and fall asleep |
|
Task_t tlHoundSleep[] = |
|
{ |
|
{ TASK_STOP_MOVING, (float)0 }, |
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE }, |
|
{ TASK_WAIT_RANDOM, (float)5 }, |
|
{ TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, |
|
{ TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, |
|
{ TASK_HOUND_FALL_ASLEEP, (float)0 }, |
|
{ TASK_WAIT_RANDOM, (float)25 }, |
|
{ TASK_HOUND_CLOSE_EYE, (float)0 }, |
|
//{ TASK_WAIT, (float)10 }, |
|
//{ TASK_WAIT_RANDOM, (float)10 }, |
|
}; |
|
|
|
Schedule_t slHoundSleep[] = |
|
{ |
|
{ |
|
tlHoundSleep, |
|
ARRAYSIZE ( tlHoundSleep ), |
|
bits_COND_HEAR_SOUND | |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE | |
|
bits_COND_NEW_ENEMY, |
|
|
|
bits_SOUND_COMBAT | |
|
bits_SOUND_PLAYER | |
|
bits_SOUND_WORLD, |
|
"Hound Sleep" |
|
}, |
|
}; |
|
|
|
// wake and stand up lazily |
|
Task_t tlHoundWakeLazy[] = |
|
{ |
|
{ TASK_STOP_MOVING, (float)0 }, |
|
{ TASK_HOUND_OPEN_EYE, (float)0 }, |
|
{ TASK_WAIT_RANDOM, (float)2.5 }, |
|
{ TASK_PLAY_SEQUENCE, (float)ACT_STAND }, |
|
{ TASK_HOUND_WAKE_UP, (float)0 }, |
|
}; |
|
|
|
Schedule_t slHoundWakeLazy[] = |
|
{ |
|
{ |
|
tlHoundWakeLazy, |
|
ARRAYSIZE ( tlHoundWakeLazy ), |
|
0, |
|
0, |
|
"WakeLazy" |
|
}, |
|
}; |
|
|
|
// wake and stand up with great urgency! |
|
Task_t tlHoundWakeUrgent[] = |
|
{ |
|
{ TASK_HOUND_OPEN_EYE, (float)0 }, |
|
{ TASK_PLAY_SEQUENCE, (float)ACT_HOP }, |
|
{ TASK_FACE_IDEAL, (float)0 }, |
|
{ TASK_HOUND_WAKE_UP, (float)0 }, |
|
}; |
|
|
|
Schedule_t slHoundWakeUrgent[] = |
|
{ |
|
{ |
|
tlHoundWakeUrgent, |
|
ARRAYSIZE ( tlHoundWakeUrgent ), |
|
0, |
|
0, |
|
"WakeUrgent" |
|
}, |
|
}; |
|
|
|
Task_t tlHoundSpecialAttack1[] = |
|
{ |
|
{ TASK_STOP_MOVING, 0 }, |
|
{ TASK_FACE_IDEAL, (float)0 }, |
|
{ TASK_SPECIAL_ATTACK1, (float)0 }, |
|
{ TASK_PLAY_SEQUENCE, (float)ACT_IDLE_ANGRY }, |
|
}; |
|
|
|
Schedule_t slHoundSpecialAttack1[] = |
|
{ |
|
{ |
|
tlHoundSpecialAttack1, |
|
ARRAYSIZE ( tlHoundSpecialAttack1 ), |
|
bits_COND_NEW_ENEMY | |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE | |
|
bits_COND_ENEMY_OCCLUDED, |
|
|
|
0, |
|
"Hound Special Attack1" |
|
}, |
|
}; |
|
|
|
Task_t tlHoundAgitated[] = |
|
{ |
|
{ TASK_STOP_MOVING, 0 }, |
|
{ TASK_HOUND_THREAT_DISPLAY, 0 }, |
|
}; |
|
|
|
Schedule_t slHoundAgitated[] = |
|
{ |
|
{ |
|
tlHoundAgitated, |
|
ARRAYSIZE ( tlHoundAgitated ), |
|
bits_COND_NEW_ENEMY | |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE, |
|
0, |
|
"Hound Agitated" |
|
}, |
|
}; |
|
|
|
Task_t tlHoundHopRetreat[] = |
|
{ |
|
{ TASK_STOP_MOVING, 0 }, |
|
{ TASK_HOUND_HOP_BACK, 0 }, |
|
{ TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, |
|
}; |
|
|
|
Schedule_t slHoundHopRetreat[] = |
|
{ |
|
{ |
|
tlHoundHopRetreat, |
|
ARRAYSIZE ( tlHoundHopRetreat ), |
|
0, |
|
0, |
|
"Hound Hop Retreat" |
|
}, |
|
}; |
|
|
|
// hound fails in combat with client in the PVS |
|
Task_t tlHoundCombatFailPVS[] = |
|
{ |
|
{ TASK_STOP_MOVING, 0 }, |
|
{ TASK_HOUND_THREAT_DISPLAY, 0 }, |
|
{ TASK_WAIT_FACE_ENEMY, 1.0f }, |
|
}; |
|
|
|
Schedule_t slHoundCombatFailPVS[] = |
|
{ |
|
{ |
|
tlHoundCombatFailPVS, |
|
ARRAYSIZE( tlHoundCombatFailPVS ), |
|
bits_COND_NEW_ENEMY | |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE, |
|
0, |
|
"HoundCombatFailPVS" |
|
}, |
|
}; |
|
|
|
// hound fails in combat with no client in the PVS. Don't keep peeping! |
|
Task_t tlHoundCombatFailNoPVS[] = |
|
{ |
|
{ TASK_STOP_MOVING, 0 }, |
|
{ TASK_HOUND_THREAT_DISPLAY, 0 }, |
|
{ TASK_WAIT_FACE_ENEMY, (float)2 }, |
|
{ TASK_SET_ACTIVITY, (float)ACT_IDLE }, |
|
{ TASK_WAIT_PVS, 0 }, |
|
}; |
|
|
|
Schedule_t slHoundCombatFailNoPVS[] = |
|
{ |
|
{ |
|
tlHoundCombatFailNoPVS, |
|
ARRAYSIZE ( tlHoundCombatFailNoPVS ), |
|
bits_COND_NEW_ENEMY | |
|
bits_COND_LIGHT_DAMAGE | |
|
bits_COND_HEAVY_DAMAGE, |
|
0, |
|
"HoundCombatFailNoPVS" |
|
}, |
|
}; |
|
|
|
DEFINE_CUSTOM_SCHEDULES( CHoundeye ) |
|
{ |
|
slHoundGuardPack, |
|
slHoundRangeAttack, |
|
&slHoundRangeAttack[ 1 ], |
|
slHoundSleep, |
|
slHoundWakeLazy, |
|
slHoundWakeUrgent, |
|
slHoundSpecialAttack1, |
|
slHoundAgitated, |
|
slHoundHopRetreat, |
|
slHoundCombatFailPVS, |
|
slHoundCombatFailNoPVS, |
|
}; |
|
|
|
IMPLEMENT_CUSTOM_SCHEDULES( CHoundeye, CSquadMonster ) |
|
|
|
//========================================================= |
|
// GetScheduleOfType |
|
//========================================================= |
|
Schedule_t *CHoundeye::GetScheduleOfType( int Type ) |
|
{ |
|
if( m_fAsleep ) |
|
{ |
|
// if the hound is sleeping, must wake and stand! |
|
if( HasConditions( bits_COND_HEAR_SOUND ) ) |
|
{ |
|
CSound *pWakeSound; |
|
|
|
pWakeSound = PBestSound(); |
|
ASSERT( pWakeSound != NULL ); |
|
if( pWakeSound ) |
|
{ |
|
MakeIdealYaw( pWakeSound->m_vecOrigin ); |
|
|
|
if( FLSoundVolume( pWakeSound ) >= HOUNDEYE_SOUND_STARTLE_VOLUME ) |
|
{ |
|
// awakened by a loud sound |
|
return &slHoundWakeUrgent[0]; |
|
} |
|
} |
|
// sound was not loud enough to scare the bejesus out of houndeye |
|
return &slHoundWakeLazy[0]; |
|
} |
|
else if( HasConditions( bits_COND_NEW_ENEMY ) ) |
|
{ |
|
// get up fast, to fight. |
|
return &slHoundWakeUrgent[0]; |
|
} |
|
else |
|
{ |
|
// hound is waking up on its own |
|
return &slHoundWakeLazy[0]; |
|
} |
|
} |
|
switch( Type ) |
|
{ |
|
case SCHED_IDLE_STAND: |
|
{ |
|
// we may want to sleep instead of stand! |
|
if( InSquad() && !IsLeader() && !m_fAsleep && RANDOM_LONG( 0, 29 ) < 1 ) |
|
{ |
|
return &slHoundSleep[0]; |
|
} |
|
else |
|
{ |
|
return CSquadMonster::GetScheduleOfType( Type ); |
|
} |
|
} |
|
case SCHED_RANGE_ATTACK1: |
|
{ |
|
return &slHoundRangeAttack[0]; |
|
/* |
|
if( InSquad() ) |
|
{ |
|
return &slHoundRangeAttack[RANDOM_LONG( 0, 1 )]; |
|
} |
|
|
|
return &slHoundRangeAttack[1]; |
|
*/ |
|
} |
|
case SCHED_SPECIAL_ATTACK1: |
|
{ |
|
return &slHoundSpecialAttack1[0]; |
|
} |
|
case SCHED_GUARD: |
|
{ |
|
return &slHoundGuardPack[0]; |
|
} |
|
case SCHED_HOUND_AGITATED: |
|
{ |
|
return &slHoundAgitated[0]; |
|
} |
|
case SCHED_HOUND_HOP_RETREAT: |
|
{ |
|
return &slHoundHopRetreat[0]; |
|
} |
|
case SCHED_FAIL: |
|
{ |
|
if( m_MonsterState == MONSTERSTATE_COMBAT ) |
|
{ |
|
if( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) |
|
{ |
|
// client in PVS |
|
return &slHoundCombatFailPVS[0]; |
|
} |
|
else |
|
{ |
|
// client has taken off! |
|
return &slHoundCombatFailNoPVS[0]; |
|
} |
|
} |
|
else |
|
{ |
|
return CSquadMonster::GetScheduleOfType( Type ); |
|
} |
|
} |
|
default: |
|
{ |
|
return CSquadMonster::GetScheduleOfType( Type ); |
|
} |
|
} |
|
} |
|
|
|
//========================================================= |
|
// GetSchedule |
|
//========================================================= |
|
Schedule_t *CHoundeye::GetSchedule( void ) |
|
{ |
|
switch( m_MonsterState ) |
|
{ |
|
case MONSTERSTATE_COMBAT: |
|
{ |
|
// dead enemy |
|
if( HasConditions( bits_COND_ENEMY_DEAD ) ) |
|
{ |
|
// call base class, all code to handle dead enemies is centralized there. |
|
return CBaseMonster::GetSchedule(); |
|
} |
|
|
|
if( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) |
|
{ |
|
if( RANDOM_FLOAT( 0.0f, 1.0f ) <= 0.4f ) |
|
{ |
|
TraceResult tr; |
|
UTIL_MakeVectors( pev->angles ); |
|
UTIL_TraceHull( pev->origin, pev->origin + gpGlobals->v_forward * -128, dont_ignore_monsters, head_hull, ENT( pev ), &tr ); |
|
|
|
if( tr.flFraction == 1.0f ) |
|
{ |
|
// it's clear behind, so the hound will jump |
|
return GetScheduleOfType( SCHED_HOUND_HOP_RETREAT ); |
|
} |
|
} |
|
|
|
return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); |
|
} |
|
|
|
if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) |
|
{ |
|
if( OccupySlot( bits_SLOTS_HOUND_ATTACK ) ) |
|
{ |
|
return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); |
|
} |
|
|
|
return GetScheduleOfType( SCHED_HOUND_AGITATED ); |
|
} |
|
break; |
|
} |
|
default: |
|
break; |
|
} |
|
|
|
return CSquadMonster::GetSchedule(); |
|
}
|
|
|