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.
1287 lines
31 KiB
1287 lines
31 KiB
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Cute hound like Alien.
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "game.h"
|
|
#include "ai_default.h"
|
|
#include "ai_schedule.h"
|
|
#include "ai_hull.h"
|
|
#include "ai_navigator.h"
|
|
#include "ai_route.h"
|
|
#include "ai_squad.h"
|
|
#include "ai_squadslot.h"
|
|
#include "ai_hint.h"
|
|
#include "npcevent.h"
|
|
#include "animation.h"
|
|
#include "hl1_npc_houndeye.h"
|
|
#include "gib.h"
|
|
#include "soundent.h"
|
|
#include "ndebugoverlay.h"
|
|
#include "vstdlib/random.h"
|
|
#include "engine/IEngineSound.h"
|
|
#include "movevars_shared.h"
|
|
|
|
// 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_SQUAD_BONUS (float)1.1
|
|
|
|
#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
|
|
|
|
#define HOUNDEYE_TOP_MASS 300.0f
|
|
|
|
ConVar sk_houndeye_health ( "sk_houndeye_health", "20" );
|
|
ConVar sk_houndeye_dmg_blast ( "sk_houndeye_dmg_blast", "15" );
|
|
|
|
static int s_iSquadIndex = 0;
|
|
|
|
//=========================================================
|
|
// 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
|
|
|
|
BEGIN_DATADESC( CNPC_Houndeye )
|
|
DEFINE_FIELD( m_iSpriteTexture, FIELD_INTEGER ),
|
|
DEFINE_FIELD( m_fAsleep, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_fDontBlink, FIELD_BOOLEAN ),
|
|
DEFINE_FIELD( m_vecPackCenter, FIELD_POSITION_VECTOR ),
|
|
END_DATADESC()
|
|
|
|
LINK_ENTITY_TO_CLASS( monster_houndeye, CNPC_Houndeye );
|
|
|
|
//=========================================================
|
|
// monster-specific 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,
|
|
};
|
|
|
|
//=========================================================
|
|
// monster-specific schedule types
|
|
//=========================================================
|
|
enum
|
|
{
|
|
SCHED_HOUND_AGITATED = LAST_SHARED_SCHEDULE,
|
|
SCHED_HOUND_HOP_RETREAT,
|
|
SCHED_HOUND_YELL1,
|
|
SCHED_HOUND_YELL2,
|
|
SCHED_HOUND_RANGEATTACK,
|
|
SCHED_HOUND_SLEEP,
|
|
SCHED_HOUND_WAKE_LAZY,
|
|
SCHED_HOUND_WAKE_URGENT,
|
|
SCHED_HOUND_SPECIALATTACK,
|
|
SCHED_HOUND_COMBAT_FAIL_PVS,
|
|
SCHED_HOUND_COMBAT_FAIL_NOPVS,
|
|
// SCHED_HOUND_FAIL,
|
|
};
|
|
|
|
enum HoundEyeSquadSlots
|
|
{
|
|
SQUAD_SLOTS_HOUND_ATTACK = LAST_SHARED_SQUADSLOT,
|
|
};
|
|
|
|
|
|
//=========================================================
|
|
// Spawn
|
|
//=========================================================
|
|
void CNPC_Houndeye::Spawn()
|
|
{
|
|
Precache( );
|
|
|
|
SetRenderColor( 255, 255, 255, 255 );
|
|
|
|
SetModel( "models/houndeye.mdl" );
|
|
|
|
SetHullType(HULL_TINY);
|
|
SetHullSizeNormal();
|
|
|
|
SetSolid( SOLID_BBOX );
|
|
AddSolidFlags( FSOLID_NOT_STANDABLE );
|
|
SetMoveType( MOVETYPE_STEP );
|
|
m_bloodColor = BLOOD_COLOR_YELLOW;
|
|
ClearEffects();
|
|
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;
|
|
|
|
CapabilitiesClear();
|
|
|
|
CapabilitiesAdd( bits_CAP_MOVE_GROUND | bits_CAP_INNATE_RANGE_ATTACK1 );
|
|
CapabilitiesAdd( bits_CAP_SQUAD);
|
|
|
|
NPCInit();
|
|
}
|
|
|
|
//=========================================================
|
|
// Precache - precaches all resources this monster needs
|
|
//=========================================================
|
|
void CNPC_Houndeye::Precache()
|
|
{
|
|
PrecacheModel("models/houndeye.mdl");
|
|
|
|
m_iSpriteTexture = PrecacheModel( "sprites/shockwave.vmt" );
|
|
|
|
PrecacheScriptSound( "HoundEye.Idle" );
|
|
PrecacheScriptSound( "HoundEye.Warn" );
|
|
PrecacheScriptSound( "HoundEye.Hunt" );
|
|
PrecacheScriptSound( "HoundEye.Alert" );
|
|
PrecacheScriptSound( "HoundEye.Die" );
|
|
PrecacheScriptSound( "HoundEye.Pain" );
|
|
PrecacheScriptSound( "HoundEye.Anger1" );
|
|
PrecacheScriptSound( "HoundEye.Anger2" );
|
|
PrecacheScriptSound( "HoundEye.Sonic" );
|
|
|
|
BaseClass::Precache();
|
|
}
|
|
|
|
void CNPC_Houndeye::Event_Killed( const CTakeDamageInfo &info )
|
|
{
|
|
// Close the eye to make death more obvious
|
|
m_nSkin = 1;
|
|
BaseClass::Event_Killed( info );
|
|
}
|
|
|
|
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;
|
|
UTIL_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_ALIEN_MONSTER)
|
|
{
|
|
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;
|
|
}
|
|
|
|
//=========================================================
|
|
// IdleSound
|
|
//=========================================================
|
|
void CNPC_Houndeye::IdleSound ( void )
|
|
{
|
|
CPASAttenuationFilter filter( this );
|
|
EmitSound( filter, entindex(), "HoundEye.Idle" );
|
|
}
|
|
|
|
//=========================================================
|
|
// IdleSound
|
|
//=========================================================
|
|
void CNPC_Houndeye::WarmUpSound ( void )
|
|
{
|
|
CPASAttenuationFilter filter( this );
|
|
EmitSound( filter, entindex(),"HoundEye.Warn" );
|
|
}
|
|
|
|
//=========================================================
|
|
// WarnSound
|
|
//=========================================================
|
|
void CNPC_Houndeye::WarnSound ( void )
|
|
{
|
|
CPASAttenuationFilter filter( this );
|
|
EmitSound( filter, entindex(), "HoundEye.Hunt" );
|
|
}
|
|
|
|
//=========================================================
|
|
// AlertSound
|
|
//=========================================================
|
|
void CNPC_Houndeye::AlertSound ( void )
|
|
{
|
|
|
|
if ( m_pSquad && !m_pSquad->IsLeader( this ) )
|
|
return; // only leader makes ALERT sound.
|
|
|
|
CPASAttenuationFilter filter( this );
|
|
EmitSound( filter, entindex(), "HoundEye.Alert" );
|
|
}
|
|
|
|
//=========================================================
|
|
// DeathSound
|
|
//=========================================================
|
|
void CNPC_Houndeye::DeathSound( const CTakeDamageInfo &info )
|
|
{
|
|
CPASAttenuationFilter filter( this );
|
|
EmitSound( filter, entindex(), "HoundEye.Die" );
|
|
}
|
|
|
|
//=========================================================
|
|
// PainSound
|
|
//=========================================================
|
|
void CNPC_Houndeye::PainSound ( const CTakeDamageInfo &info )
|
|
{
|
|
CPASAttenuationFilter filter( this );
|
|
EmitSound( filter, entindex(), "HoundEye.Pain" );
|
|
}
|
|
|
|
//=========================================================
|
|
// MaxYawSpeed - allows each sequence to have a different
|
|
// turn rate associated with it.
|
|
//=========================================================
|
|
float CNPC_Houndeye::MaxYawSpeed ( void )
|
|
{
|
|
int flYS;
|
|
|
|
flYS = 90;
|
|
|
|
switch ( GetActivity() )
|
|
{
|
|
case ACT_CROUCHIDLE://sleeping!
|
|
flYS = 0;
|
|
break;
|
|
case ACT_IDLE:
|
|
flYS = 60;
|
|
break;
|
|
case ACT_WALK:
|
|
flYS = 90;
|
|
break;
|
|
case ACT_RUN:
|
|
flYS = 90;
|
|
break;
|
|
case ACT_TURN_LEFT:
|
|
case ACT_TURN_RIGHT:
|
|
flYS = 90;
|
|
break;
|
|
}
|
|
|
|
return flYS;
|
|
}
|
|
|
|
//=========================================================
|
|
// Classify - indicates this monster's place in the
|
|
// relationship table.
|
|
//=========================================================
|
|
Class_T CNPC_Houndeye::Classify ( void )
|
|
{
|
|
return CLASS_ALIEN_MONSTER;
|
|
}
|
|
|
|
//=========================================================
|
|
// 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();
|
|
Vector v_forward;
|
|
GetVectors( &v_forward, NULL, NULL );
|
|
|
|
SetGroundEntity( NULL );
|
|
|
|
Vector vecVel = v_forward * -200;
|
|
vecVel.z += ( 0.6 * flGravity ) * 0.5;
|
|
SetAbsVelocity( vecVel );
|
|
|
|
break;
|
|
}
|
|
|
|
case HOUND_AE_THUMP:
|
|
// emit the shockwaves
|
|
SonicAttack();
|
|
break;
|
|
|
|
case HOUND_AE_ANGERSOUND1:
|
|
{
|
|
CPASAttenuationFilter filter( this );
|
|
EmitSound( filter, entindex(), "HoundEye.Anger1" );
|
|
}
|
|
break;
|
|
|
|
case HOUND_AE_ANGERSOUND2:
|
|
{
|
|
CPASAttenuationFilter filter2( this );
|
|
EmitSound( filter2, entindex(), "HoundEye.Anger2" );
|
|
}
|
|
break;
|
|
|
|
case HOUND_AE_CLOSE_EYE:
|
|
if ( !m_fDontBlink )
|
|
{
|
|
m_nSkin = HOUNDEYE_EYE_FRAMES - 1;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
BaseClass::HandleAnimEvent( pEvent );
|
|
break;
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// SonicAttack
|
|
//=========================================================
|
|
void CNPC_Houndeye::SonicAttack ( void )
|
|
{
|
|
float flAdjustedDamage;
|
|
float flDist;
|
|
|
|
CPASAttenuationFilter filter( this );
|
|
EmitSound( filter, entindex(), "HoundEye.Sonic");
|
|
|
|
CBroadcastRecipientFilter filter2;
|
|
te->BeamRingPoint( filter2, 0.0,
|
|
GetAbsOrigin(), //origin
|
|
16, //start radius
|
|
HOUNDEYE_MAX_ATTACK_RADIUS,//end radius
|
|
m_iSpriteTexture, //texture
|
|
0, //halo index
|
|
0, //start frame
|
|
0, //framerate
|
|
0.2, //life
|
|
24, //width
|
|
16, //spread
|
|
0, //amplitude
|
|
WriteBeamColor().x, //r
|
|
WriteBeamColor().y, //g
|
|
WriteBeamColor().z, //b
|
|
192, //a
|
|
0 //speed
|
|
);
|
|
|
|
CBroadcastRecipientFilter filter3;
|
|
te->BeamRingPoint( filter3, 0.0,
|
|
GetAbsOrigin(), //origin
|
|
16, //start radius
|
|
HOUNDEYE_MAX_ATTACK_RADIUS / 2, //end radius
|
|
m_iSpriteTexture, //texture
|
|
0, //halo index
|
|
0, //start frame
|
|
0, //framerate
|
|
0.2, //life
|
|
24, //width
|
|
16, //spread
|
|
0, //amplitude
|
|
WriteBeamColor().x, //r
|
|
WriteBeamColor().y, //g
|
|
WriteBeamColor().z, //b
|
|
192, //a
|
|
0 //speed
|
|
);
|
|
|
|
CBaseEntity *pEntity = NULL;
|
|
// iterate on all entities in the vicinity.
|
|
while ((pEntity = gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), HOUNDEYE_MAX_ATTACK_RADIUS )) != NULL)
|
|
{
|
|
if ( pEntity->m_takedamage != DAMAGE_NO )
|
|
{
|
|
if ( !FClassnameIs(pEntity, "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 ( m_pSquad && m_pSquad->NumMembers() > 1 )
|
|
{
|
|
// squad gets attack bonus.
|
|
flAdjustedDamage = sk_houndeye_dmg_blast.GetFloat() + sk_houndeye_dmg_blast.GetFloat() * ( HOUNDEYE_SQUAD_BONUS * ( m_pSquad->NumMembers() - 1 ) );
|
|
}
|
|
else
|
|
{
|
|
// solo
|
|
flAdjustedDamage =sk_houndeye_dmg_blast.GetFloat();
|
|
}
|
|
|
|
flDist = (pEntity->WorldSpaceCenter() - GetAbsOrigin()).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.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
|
|
flAdjustedDamage = 0;
|
|
}
|
|
}
|
|
|
|
//ALERT ( at_aiconsole, "Damage: %f\n", flAdjustedDamage );
|
|
|
|
if (flAdjustedDamage > 0 )
|
|
{
|
|
CTakeDamageInfo info( this, this, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB );
|
|
CalculateExplosiveDamageForce( &info, (pEntity->GetAbsOrigin() - GetAbsOrigin()), pEntity->GetAbsOrigin() );
|
|
|
|
pEntity->TakeDamage( info );
|
|
|
|
if ( (pEntity->GetAbsOrigin() - GetAbsOrigin()).Length2D() <= HOUNDEYE_MAX_ATTACK_RADIUS )
|
|
{
|
|
if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS || (pEntity->VPhysicsGetObject() && !pEntity->IsPlayer()) )
|
|
{
|
|
IPhysicsObject *pPhysObject = pEntity->VPhysicsGetObject();
|
|
|
|
if ( pPhysObject )
|
|
{
|
|
float flMass = pPhysObject->GetMass();
|
|
|
|
if ( flMass <= HOUNDEYE_TOP_MASS )
|
|
{
|
|
// Increase the vertical lift of the force
|
|
Vector vecForce = info.GetDamageForce();
|
|
vecForce.z *= 2.0f;
|
|
info.SetDamageForce( vecForce );
|
|
|
|
pEntity->VPhysicsTakeDamage( info );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// WriteBeamColor - writes a color vector to the network
|
|
// based on the size of the group.
|
|
//=========================================================
|
|
Vector CNPC_Houndeye::WriteBeamColor ( void )
|
|
{
|
|
BYTE bRed, bGreen, bBlue;
|
|
|
|
if ( m_pSquad )
|
|
{
|
|
switch ( m_pSquad->NumMembers() )
|
|
{
|
|
case 1:
|
|
// solo houndeye - weakest beam
|
|
bRed = 188;
|
|
bGreen = 220;
|
|
bBlue = 255;
|
|
break;
|
|
case 2:
|
|
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:
|
|
Msg ( "Unsupported Houndeye SquadSize!\n" );
|
|
bRed = 188;
|
|
bGreen = 220;
|
|
bBlue = 255;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// solo houndeye - weakest beam
|
|
bRed = 188;
|
|
bGreen = 220;
|
|
bBlue = 255;
|
|
}
|
|
|
|
|
|
return Vector ( bRed, bGreen, bBlue );
|
|
}
|
|
|
|
bool CNPC_Houndeye::ShouldGoToIdleState( void )
|
|
{
|
|
if ( m_pSquad )
|
|
{
|
|
AISquadIter_t iter;
|
|
for (CAI_BaseNPC *pMember = m_pSquad->GetFirstMember( &iter ); pMember; pMember = m_pSquad->GetNextMember( &iter ) )
|
|
{
|
|
if ( pMember != this && pMember->GetHintNode() && pMember->GetHintNode()->HintType() != NO_NODE )
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CNPC_Houndeye::FValidateHintType ( CAI_Hint *pHint )
|
|
{
|
|
switch( pHint->HintType() )
|
|
{
|
|
case HINT_HL1_WORLD_MACHINERY:
|
|
return true;
|
|
break;
|
|
case HINT_HL1_WORLD_BLINKING_LIGHT:
|
|
return true;
|
|
break;
|
|
case HINT_HL1_WORLD_HUMAN_BLOOD:
|
|
return true;
|
|
break;
|
|
case HINT_HL1_WORLD_ALIEN_BLOOD:
|
|
return true;
|
|
break;
|
|
}
|
|
|
|
Msg ( "Couldn't validate hint type" );
|
|
|
|
return false;
|
|
}
|
|
|
|
//=========================================================
|
|
// SetActivity
|
|
//=========================================================
|
|
void CNPC_Houndeye::SetActivity ( Activity NewActivity )
|
|
{
|
|
int iSequence;
|
|
|
|
if ( NewActivity == GetActivity() )
|
|
return;
|
|
|
|
if ( m_NPCState == NPC_STATE_COMBAT && NewActivity == ACT_IDLE && random->RandomInt( 0, 1 ) )
|
|
{
|
|
// play pissed idle.
|
|
iSequence = LookupSequence( "madidle" );
|
|
|
|
SetActivity( 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
|
|
SetIdealActivity( GetActivity() );
|
|
|
|
// Set to the desired anim, or default anim if the desired is not present
|
|
if ( iSequence > ACTIVITY_NOT_AVAILABLE )
|
|
{
|
|
SetSequence( iSequence ); // Set to the reset anim (if it's there)
|
|
SetCycle( 0 ); // FIX: frame counter shouldn't be reset when its the same activity as before
|
|
ResetSequenceInfo();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BaseClass::SetActivity ( NewActivity );
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// start task
|
|
//=========================================================
|
|
void CNPC_Houndeye::StartTask ( const Task_t *pTask )
|
|
{
|
|
switch ( pTask->iTask )
|
|
{
|
|
case TASK_HOUND_FALL_ASLEEP:
|
|
{
|
|
m_fAsleep = TRUE; // signal that hound is lying down (must stand again before doing anything else!)
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_HOUND_WAKE_UP:
|
|
{
|
|
m_fAsleep = FALSE; // signal that hound is standing again
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_HOUND_OPEN_EYE:
|
|
{
|
|
m_fDontBlink = FALSE; // turn blinking back on and that code will automatically open the eye
|
|
TaskComplete();
|
|
break;
|
|
}
|
|
case TASK_HOUND_CLOSE_EYE:
|
|
{
|
|
m_nSkin = 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;
|
|
}
|
|
case TASK_SPECIAL_ATTACK1:
|
|
{
|
|
SetIdealActivity( ACT_SPECIAL_ATTACK1 );
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
BaseClass::StartTask(pTask);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// RunTask
|
|
//=========================================================
|
|
void CNPC_Houndeye::RunTask ( const Task_t *pTask )
|
|
{
|
|
switch ( pTask->iTask )
|
|
{
|
|
case TASK_HOUND_THREAT_DISPLAY:
|
|
{
|
|
if ( GetEnemy() )
|
|
{
|
|
float idealYaw;
|
|
idealYaw = UTIL_VecToYaw( GetEnemy()->GetAbsOrigin() - GetAbsOrigin() );
|
|
GetMotor()->SetIdealYawAndUpdate( idealYaw );
|
|
}
|
|
|
|
if ( IsSequenceFinished() )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
|
|
break;
|
|
}
|
|
case TASK_HOUND_CLOSE_EYE:
|
|
{
|
|
if ( m_nSkin < HOUNDEYE_EYE_FRAMES - 1 )
|
|
m_nSkin++;
|
|
break;
|
|
}
|
|
case TASK_HOUND_HOP_BACK:
|
|
{
|
|
if ( IsSequenceFinished() )
|
|
{
|
|
TaskComplete();
|
|
}
|
|
break;
|
|
}
|
|
case TASK_SPECIAL_ATTACK1:
|
|
{
|
|
m_nSkin = random->RandomInt(0, HOUNDEYE_EYE_FRAMES - 1);
|
|
|
|
float idealYaw;
|
|
idealYaw = UTIL_VecToYaw( GetNavigator()->GetGoalPos() );
|
|
GetMotor()->SetIdealYawAndUpdate( idealYaw );
|
|
|
|
float life;
|
|
life = ((255 - GetCycle()) / ( m_flPlaybackRate * m_flPlaybackRate));
|
|
if (life < 0.1)
|
|
{
|
|
life = 0.1;
|
|
}
|
|
|
|
/* MessageBegin( MSG_PAS, SVC_TEMPENTITY, GetAbsOrigin() );
|
|
WRITE_BYTE( TE_IMPLOSION);
|
|
WRITE_COORD( GetAbsOrigin().x);
|
|
WRITE_COORD( GetAbsOrigin().y);
|
|
WRITE_COORD( GetAbsOrigin().z + 16);
|
|
WRITE_BYTE( 50 * life + 100); // radius
|
|
WRITE_BYTE( pev->frame / 25.0 ); // count
|
|
WRITE_BYTE( life * 10 ); // life
|
|
MessageEnd();*/
|
|
|
|
if ( IsSequenceFinished() )
|
|
{
|
|
SonicAttack();
|
|
TaskComplete();
|
|
}
|
|
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
BaseClass::RunTask(pTask);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// PrescheduleThink
|
|
//=========================================================
|
|
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 )
|
|
{
|
|
if ( ( m_nSkin == 0 ) && random->RandomInt(0,0x7F) == 0 )
|
|
{// start blinking!
|
|
m_nSkin = HOUNDEYE_EYE_FRAMES - 1;
|
|
}
|
|
else if ( m_nSkin != 0 )
|
|
{// already blinking
|
|
m_nSkin--;
|
|
}
|
|
}
|
|
|
|
// if you are the leader, average the origins of each pack member to get an approximate center.
|
|
if ( m_pSquad && m_pSquad->IsLeader( this ) )
|
|
{
|
|
int iSquadCount = 0;
|
|
|
|
AISquadIter_t iter;
|
|
for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
|
|
{
|
|
iSquadCount++;
|
|
m_vecPackCenter = m_vecPackCenter + pSquadMember->GetAbsOrigin();
|
|
}
|
|
|
|
m_vecPackCenter = m_vecPackCenter / iSquadCount;
|
|
}
|
|
}
|
|
|
|
//=========================================================
|
|
// GetScheduleOfType
|
|
//=========================================================
|
|
int CNPC_Houndeye::TranslateSchedule( int scheduleType )
|
|
{
|
|
if ( m_fAsleep )
|
|
{
|
|
// if the hound is sleeping, must wake and stand!
|
|
if ( HasCondition( COND_HEAR_DANGER ) || HasCondition( COND_HEAR_THUMPER ) || HasCondition( COND_HEAR_COMBAT ) ||
|
|
HasCondition( COND_HEAR_WORLD ) || HasCondition( COND_HEAR_PLAYER ) || HasCondition( COND_HEAR_BULLET_IMPACT ) )
|
|
{
|
|
CSound *pWakeSound;
|
|
|
|
pWakeSound = GetBestSound();
|
|
ASSERT( pWakeSound != NULL );
|
|
if ( pWakeSound )
|
|
{
|
|
GetMotor()->SetIdealYawToTarget ( pWakeSound->GetSoundOrigin() );
|
|
|
|
if ( FLSoundVolume ( pWakeSound ) >= HOUNDEYE_SOUND_STARTLE_VOLUME )
|
|
{
|
|
// awakened by a loud sound
|
|
return SCHED_HOUND_WAKE_URGENT;
|
|
}
|
|
}
|
|
// sound was not loud enough to scare the bejesus out of houndeye
|
|
return SCHED_HOUND_WAKE_LAZY;
|
|
}
|
|
else if ( HasCondition( COND_NEW_ENEMY ) )
|
|
{
|
|
// get up fast, to fight.
|
|
return SCHED_HOUND_WAKE_URGENT;
|
|
}
|
|
|
|
else
|
|
{
|
|
// hound is waking up on its own
|
|
return SCHED_HOUND_WAKE_LAZY;
|
|
}
|
|
}
|
|
switch ( scheduleType )
|
|
{
|
|
case SCHED_IDLE_STAND:
|
|
{
|
|
// we may want to sleep instead of stand!
|
|
if ( m_pSquad && !m_pSquad->IsLeader( this ) && !m_fAsleep && random->RandomInt( 0,29 ) < 1 )
|
|
{
|
|
return SCHED_HOUND_SLEEP;
|
|
}
|
|
else
|
|
{
|
|
return BaseClass::TranslateSchedule( scheduleType );
|
|
}
|
|
}
|
|
|
|
case SCHED_RANGE_ATTACK1:
|
|
return SCHED_HOUND_RANGEATTACK;
|
|
case SCHED_SPECIAL_ATTACK1:
|
|
return SCHED_HOUND_SPECIALATTACK;
|
|
|
|
case SCHED_FAIL:
|
|
{
|
|
if ( m_NPCState == NPC_STATE_COMBAT )
|
|
{
|
|
if ( !FNullEnt( UTIL_FindClientInPVS( edict() ) ) )
|
|
{
|
|
// client in PVS
|
|
return SCHED_HOUND_COMBAT_FAIL_PVS;
|
|
}
|
|
else
|
|
{
|
|
// client has taken off!
|
|
return SCHED_HOUND_COMBAT_FAIL_NOPVS;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return BaseClass::TranslateSchedule ( scheduleType );
|
|
}
|
|
}
|
|
default:
|
|
{
|
|
return BaseClass::TranslateSchedule ( scheduleType );
|
|
}
|
|
}
|
|
}
|
|
|
|
int CNPC_Houndeye::SelectSchedule( void )
|
|
{
|
|
switch ( m_NPCState )
|
|
{
|
|
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 ( HasCondition( COND_LIGHT_DAMAGE ) || HasCondition( COND_HEAVY_DAMAGE ) )
|
|
{
|
|
if ( random->RandomFloat( 0 , 1 ) <= 0.4 )
|
|
{
|
|
trace_t trace;
|
|
Vector v_forward;
|
|
GetVectors( &v_forward, NULL, NULL );
|
|
UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin() + v_forward * -128, MASK_SOLID, &trace );
|
|
|
|
if ( trace.fraction == 1.0 )
|
|
{
|
|
// it's clear behind, so the hound will jump
|
|
return SCHED_HOUND_HOP_RETREAT;
|
|
}
|
|
}
|
|
|
|
return SCHED_TAKE_COVER_FROM_ENEMY;
|
|
}
|
|
|
|
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
|
|
{
|
|
// don't constraint attacks based on squad slots, just let em all go for it
|
|
// if ( OccupyStrategySlot ( SQUAD_SLOTS_HOUND_ATTACK ) )
|
|
return SCHED_RANGE_ATTACK1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return BaseClass::SelectSchedule();
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
// FLSoundVolume - subtracts the volume of the given sound
|
|
// from the distance the sound source is from the caller,
|
|
// and returns that value, which is considered to be the 'local'
|
|
// volume of the sound.
|
|
//=========================================================
|
|
float CNPC_Houndeye::FLSoundVolume( CSound *pSound )
|
|
{
|
|
return ( pSound->Volume() - ( ( pSound->GetSoundOrigin() - GetAbsOrigin() ).Length() ) );
|
|
}
|
|
|
|
|
|
//=========================================================
|
|
//
|
|
// SquadRecruit(), get some monsters of my classification and
|
|
// link them as a group. returns the group size
|
|
//
|
|
//=========================================================
|
|
int CNPC_Houndeye::SquadRecruit( int searchRadius, int maxMembers )
|
|
{
|
|
int squadCount;
|
|
int iMyClass = Classify();// cache this monster's class
|
|
|
|
if ( maxMembers < 2 )
|
|
return 0;
|
|
|
|
// I am my own leader
|
|
squadCount = 1;
|
|
|
|
CBaseEntity *pEntity = NULL;
|
|
|
|
if ( m_SquadName != NULL_STRING )
|
|
{
|
|
// I have a netname, so unconditionally recruit everyone else with that name.
|
|
pEntity = gEntList.FindEntityByClassname( pEntity, "monster_houndeye" );
|
|
|
|
while ( pEntity && squadCount < maxMembers )
|
|
{
|
|
CNPC_Houndeye *pRecruit = (CNPC_Houndeye*)pEntity->MyNPCPointer();
|
|
|
|
if ( pRecruit )
|
|
{
|
|
if ( !pRecruit->m_pSquad && pRecruit->Classify() == iMyClass && pRecruit != this )
|
|
{
|
|
// minimum protection here against user error.in worldcraft.
|
|
if ( pRecruit->m_SquadName != NULL_STRING && FStrEq( STRING( m_SquadName ), STRING( pRecruit->m_SquadName ) ) )
|
|
{
|
|
pRecruit->InitSquad();
|
|
squadCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
pEntity = gEntList.FindEntityByClassname( pEntity, "monster_houndeye" );
|
|
}
|
|
|
|
return squadCount;
|
|
}
|
|
else
|
|
{
|
|
char szSquadName[64];
|
|
Q_snprintf( szSquadName, sizeof( szSquadName ), "squad%d\n", s_iSquadIndex );
|
|
|
|
m_SquadName = MAKE_STRING( szSquadName );
|
|
|
|
while ( ( pEntity = gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), searchRadius ) ) != NULL && squadCount < maxMembers )
|
|
{
|
|
if ( !FClassnameIs ( pEntity, "monster_houndeye" ) )
|
|
continue;
|
|
|
|
CNPC_Houndeye *pRecruit = (CNPC_Houndeye*)pEntity->MyNPCPointer();
|
|
|
|
if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_hCine )
|
|
{
|
|
// Can we recruit this guy?
|
|
if ( !pRecruit->m_pSquad && pRecruit->Classify() == iMyClass &&
|
|
( (iMyClass != CLASS_ALIEN_MONSTER) || FClassnameIs( this, pRecruit->GetClassname() ) ) &&
|
|
!pRecruit->m_SquadName )
|
|
{
|
|
trace_t tr;
|
|
UTIL_TraceLine( GetAbsOrigin() + GetViewOffset(), pRecruit->GetAbsOrigin() + GetViewOffset(), MASK_NPCSOLID_BRUSHONLY, pRecruit, COLLISION_GROUP_NONE, &tr );// try to hit recruit with a traceline.
|
|
|
|
if ( tr.fraction == 1.0 )
|
|
{
|
|
//We're ready to recruit people, so start a squad if I don't have one.
|
|
if ( !m_pSquad )
|
|
{
|
|
InitSquad();
|
|
}
|
|
|
|
pRecruit->m_SquadName = m_SquadName;
|
|
|
|
pRecruit->CapabilitiesAdd ( bits_CAP_SQUAD );
|
|
pRecruit->InitSquad();
|
|
|
|
squadCount++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( squadCount > 1 )
|
|
{
|
|
s_iSquadIndex++;
|
|
}
|
|
}
|
|
|
|
return squadCount;
|
|
}
|
|
|
|
|
|
|
|
void CNPC_Houndeye::StartNPC ( void )
|
|
{
|
|
if ( !m_pSquad )
|
|
{
|
|
if ( m_SquadName != NULL_STRING )
|
|
{
|
|
BaseClass::StartNPC();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
int iSquadSize = SquadRecruit( 1024, 4 );
|
|
|
|
if ( iSquadSize )
|
|
{
|
|
Msg ( "Squad of %d %s formed\n", iSquadSize, GetClassname() );
|
|
}
|
|
}
|
|
}
|
|
|
|
BaseClass::StartNPC();
|
|
}
|
|
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
//
|
|
// Schedules
|
|
//
|
|
//------------------------------------------------------------------------------
|
|
|
|
AI_BEGIN_CUSTOM_NPC( monster_houndeye, CNPC_Houndeye )
|
|
|
|
DECLARE_TASK ( TASK_HOUND_CLOSE_EYE )
|
|
DECLARE_TASK ( TASK_HOUND_OPEN_EYE )
|
|
DECLARE_TASK ( TASK_HOUND_THREAT_DISPLAY )
|
|
DECLARE_TASK ( TASK_HOUND_FALL_ASLEEP )
|
|
DECLARE_TASK ( TASK_HOUND_WAKE_UP )
|
|
DECLARE_TASK ( TASK_HOUND_HOP_BACK )
|
|
|
|
//=========================================================
|
|
// > SCHED_HOUND_RANGEATTACK
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_HOUND_RANGEATTACK,
|
|
|
|
" Tasks"
|
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_HOUND_YELL1"
|
|
" "
|
|
" Interrupts"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_HOUND_AGITATED
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_HOUND_AGITATED,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_HOUND_THREAT_DISPLAY 0"
|
|
" "
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_HEAVY_DAMAGE"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_HOUND_HOP_RETREAT
|
|
//=========================================================
|
|
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"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_HOUND_YELL1
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_HOUND_YELL1,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_FACE_IDEAL 0"
|
|
" TASK_RANGE_ATTACK1 0"
|
|
" TASK_SET_SCHEDULE SCHEDULE:SCHED_HOUND_AGITATED"
|
|
" "
|
|
" Interrupts"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_HOUND_YELL2
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_HOUND_YELL2,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_FACE_IDEAL 0"
|
|
" TASK_RANGE_ATTACK1 0"
|
|
" "
|
|
" Interrupts"
|
|
)
|
|
|
|
|
|
//=========================================================
|
|
// > SCHED_HOUND_SLEEP
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_HOUND_SLEEP,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
|
|
" TASK_WAIT_RANDOM 5"
|
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_CROUCH"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_CROUCHIDLE"
|
|
" TASK_HOUND_FALL_ASLEEP 0"
|
|
" TASK_WAIT_RANDOM 25"
|
|
" TASK_HOUND_CLOSE_EYE 0"
|
|
" "
|
|
" Interrupts"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_NEW_ENEMY"
|
|
" COND_HEAR_COMBAT"
|
|
" COND_HEAR_DANGER"
|
|
" COND_HEAR_PLAYER"
|
|
" COND_HEAR_WORLD"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_HOUND_WAKE_LAZY
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_HOUND_WAKE_LAZY,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_HOUND_OPEN_EYE 0"
|
|
" TASK_WAIT_RANDOM 2.5"
|
|
" TASK_PLAY_SEQUENCE ACT_STAND"
|
|
" TASK_HOUND_WAKE_UP 0"
|
|
" "
|
|
" Interrupts"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_HOUND_WAKE_URGENT
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_HOUND_WAKE_URGENT,
|
|
|
|
" Tasks"
|
|
" TASK_HOUND_OPEN_EYE 0"
|
|
" TASK_PLAY_SEQUENCE ACT_HOP"
|
|
" TASK_FACE_IDEAL 0"
|
|
" TASK_HOUND_WAKE_UP 0"
|
|
" "
|
|
" Interrupts"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_HOUND_SPECIALATTACK
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_HOUND_SPECIALATTACK,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_FACE_IDEAL 0"
|
|
" TASK_SPECIAL_ATTACK1 0"
|
|
" TASK_PLAY_SEQUENCE ACTIVITY:ACT_IDLE_ANGRY"
|
|
" "
|
|
" Interrupts"
|
|
" "
|
|
" COND_NEW_ENEMY"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
" COND_ENEMY_OCCLUDED"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_HOUND_COMBAT_FAIL_PVS
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_HOUND_COMBAT_FAIL_PVS,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_HOUND_THREAT_DISPLAY 0"
|
|
" TASK_WAIT_FACE_ENEMY 1"
|
|
" "
|
|
" Interrupts"
|
|
" "
|
|
" COND_NEW_ENEMY"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
)
|
|
|
|
//=========================================================
|
|
// > SCHED_HOUND_COMBAT_FAIL_NOPVS
|
|
//=========================================================
|
|
DEFINE_SCHEDULE
|
|
(
|
|
SCHED_HOUND_COMBAT_FAIL_NOPVS,
|
|
|
|
" Tasks"
|
|
" TASK_STOP_MOVING 0"
|
|
" TASK_HOUND_THREAT_DISPLAY 0"
|
|
" TASK_WAIT_FACE_ENEMY 1"
|
|
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE"
|
|
" TASK_WAIT_PVS 0"
|
|
" "
|
|
" Interrupts"
|
|
" COND_NEW_ENEMY"
|
|
" COND_LIGHT_DAMAGE"
|
|
" COND_HEAVY_DAMAGE"
|
|
)
|
|
|
|
AI_END_CUSTOM_NPC()
|