Portable Half-Life SDK. GoldSource and Xash3D. Crossplatform.
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

/***
*
* 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();
}