source-engine/game/server/hl1/hl1_npc_houndeye.cpp
2022-08-31 18:23:22 +03:00

1288 lines
31 KiB
C++

//========= 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 = AllocPooledString( 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()