Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.
 
 
 
 
 
 

1395 lines
36 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Houndeye - a spooky sonic dog.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "npc_houndeye.h"
#include "ai_default.h"
#include "ai_node.h"
#include "ai_route.h"
#include "AI_Navigator.h"
#include "AI_Motor.h"
#include "ai_squad.h"
#include "AI_TacticalServices.h"
#include "soundent.h"
#include "EntityList.h"
#include "game.h"
#include "activitylist.h"
#include "hl2_shareddefs.h"
#include "grenade_energy.h"
#include "energy_wave.h"
#include "ai_interactions.h"
#include "ndebugoverlay.h"
#include "npcevent.h"
#include "player.h"
#include "vstdlib/random.h"
#include "engine/IEngineSound.h"
#include "movevars_shared.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define HOUNDEYE_MAX_ATTACK_RADIUS 500
#define HOUNDEYE_MIN_ATTACK_RADIUS 100
#define HOUNDEYE_EYE_FRAMES 4 // how many different switchable maps for the eye
ConVar sk_Houndeye_health( "sk_Houndeye_health","0");
ConVar sk_Houndeye_dmg_blast( "sk_Houndeye_dmg_blast","0");
//=========================================================
// Interactions
//=========================================================
int g_interactionHoundeyeGroupAttack = 0;
int g_interactionHoundeyeGroupRetreat = 0;
int g_interactionHoundeyeGroupRalley = 0;
//=========================================================
// Specialized Tasks
//=========================================================
enum
{
TASK_HOUND_CLOSE_EYE = LAST_SHARED_TASK,
TASK_HOUND_OPEN_EYE,
TASK_HOUND_THREAT_DISPLAY,
TASK_HOUND_FALL_ASLEEP,
TASK_HOUND_WAKE_UP,
TASK_HOUND_HOP_BACK,
TASK_HOUND_GET_PATH_TO_CIRCLE,
TASK_HOUND_REVERSE_STRAFE_DIR,
};
//-----------------------------------------------------------------------------
// Custom Conditions
//-----------------------------------------------------------------------------
enum Houndeye_Conds
{
COND_HOUND_GROUP_ATTACK = LAST_SHARED_CONDITION,
COND_HOUND_GROUP_RETREAT,
COND_HOUND_GROUP_RALLEY,
};
//=========================================================
// Specialized Shedules
//=========================================================
enum
{
SCHED_HOUND_AGITATED = LAST_SHARED_SCHEDULE,
SCHED_HOUND_HOP_RETREAT,
SCHED_HOUND_RANGE_ATTACK1,
SCHED_HOUND_ATTACK_STRAFE,
SCHED_HOUND_ATTACK_STRAFE_REVERSE,
SCHED_HOUND_GROUP_ATTACK,
SCHED_HOUND_GROUP_RETREAT,
SCHED_HOUND_CHASE_ENEMY,
SCHED_HOUND_COVER_WAIT,
SCHED_HOUND_GROUP_RALLEY,
};
//=========================================================
// Specialized activities
//=========================================================
int ACT_HOUND_GUARD;
//=========================================================
// Monster's Anim Events Go Here
//=========================================================
#define HOUND_AE_WARN 1
#define HOUND_AE_STARTATTACK 2
#define HOUND_AE_THUMP 3
#define HOUND_AE_ANGERSOUND1 4
#define HOUND_AE_ANGERSOUND2 5
#define HOUND_AE_HOPBACK 6
#define HOUND_AE_CLOSE_EYE 7
#define HOUND_AE_LEAP_HIT 8
//-----------------------------------------------------------------------------
// Purpose: Initialize the custom schedules
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_Houndeye::InitCustomSchedules(void)
{
INIT_CUSTOM_AI(CNPC_Houndeye);
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_CLOSE_EYE);
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_OPEN_EYE);
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_THREAT_DISPLAY);
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_FALL_ASLEEP);
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_WAKE_UP);
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_HOP_BACK);
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_GET_PATH_TO_CIRCLE);
ADD_CUSTOM_TASK(CNPC_Houndeye, TASK_HOUND_REVERSE_STRAFE_DIR);
ADD_CUSTOM_CONDITION(CNPC_Houndeye, COND_HOUND_GROUP_ATTACK);
ADD_CUSTOM_CONDITION(CNPC_Houndeye, COND_HOUND_GROUP_RETREAT);
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_HOP_RETREAT);
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_RANGE_ATTACK1);
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_ATTACK_STRAFE);
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_ATTACK_STRAFE_REVERSE);
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_GROUP_ATTACK);
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_GROUP_RETREAT);
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_CHASE_ENEMY);
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_COVER_WAIT);
ADD_CUSTOM_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_GROUP_RALLEY);
ADD_CUSTOM_ACTIVITY(CNPC_Houndeye, ACT_HOUND_GUARD);
g_interactionHoundeyeGroupAttack = CBaseCombatCharacter::GetInteractionID();
g_interactionHoundeyeGroupRetreat = CBaseCombatCharacter::GetInteractionID();
g_interactionHoundeyeGroupRalley = CBaseCombatCharacter::GetInteractionID();
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_HOP_RETREAT);
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_RANGE_ATTACK1);
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_ATTACK_STRAFE);
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_ATTACK_STRAFE_REVERSE);
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_GROUP_ATTACK);
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_GROUP_RETREAT);
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_CHASE_ENEMY);
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_COVER_WAIT);
AI_LOAD_SCHEDULE(CNPC_Houndeye, SCHED_HOUND_GROUP_RALLEY);
}
LINK_ENTITY_TO_CLASS( npc_houndeye, CNPC_Houndeye );
IMPLEMENT_CUSTOM_AI( npc_houndeye, CNPC_Houndeye );
BEGIN_DATADESC( CNPC_Houndeye )
DEFINE_FIELD( m_fAsleep, FIELD_BOOLEAN ),
DEFINE_FIELD( m_fDontBlink, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flNextSecondaryAttack, FIELD_TIME ),
DEFINE_FIELD( m_bLoopClockwise, FIELD_BOOLEAN ),
DEFINE_FIELD( m_pEnergyWave, FIELD_CLASSPTR ),
DEFINE_FIELD( m_flEndEnergyWaveTime, FIELD_TIME ),
END_DATADESC()
//=========================================================
// Classify - indicates this monster's place in the
// relationship table.
//=========================================================
Class_T CNPC_Houndeye::Classify ( void )
{
return CLASS_HOUNDEYE;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
int CNPC_Houndeye::RangeAttack1Conditions ( float flDot, float flDist )
{
// I'm not allowed to attack if standing in another hound eye
// (note houndeyes allowed to interpenetrate)
trace_t tr;
AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,0.1),
GetHullMins(), GetHullMaxs(),
MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
if (tr.startsolid)
{
CBaseEntity *pEntity = tr.m_pEnt;
if (pEntity->Classify() == CLASS_HOUNDEYE)
{
return( COND_NONE );
}
}
// If I'm really close to my enemy allow me to attack if
// I'm facing regardless of next attack time
if (flDist < 100 && flDot >= 0.3)
{
return COND_CAN_RANGE_ATTACK1;
}
if ( gpGlobals->curtime < m_flNextAttack )
{
return( COND_NONE );
}
if (flDist > ( HOUNDEYE_MAX_ATTACK_RADIUS * 0.5 ))
{
return COND_TOO_FAR_TO_ATTACK;
}
if (flDot < 0.3)
{
return COND_NOT_FACING_ATTACK;
}
return COND_CAN_RANGE_ATTACK1;
}
//-----------------------------------------------------------------------------
// Purpose: Overidden for human grunts because they hear the DANGER sound
// Input :
// Output :
//-----------------------------------------------------------------------------
int CNPC_Houndeye::GetSoundInterests( void )
{
return SOUND_WORLD |
SOUND_COMBAT |
SOUND_PLAYER |
SOUND_DANGER;
}
//=========================================================
// MaxYawSpeed - allows each sequence to have a different
// turn rate associated with it.
//=========================================================
float CNPC_Houndeye::MaxYawSpeed ( void )
{
int ys = 90;
switch ( GetActivity() )
{
case ACT_CROUCHIDLE://sleeping!
ys = 0;
break;
case ACT_IDLE:
ys = 60;
break;
case ACT_WALK:
ys = 90;
break;
case ACT_RUN:
ys = 90;
break;
case ACT_TURN_LEFT:
case ACT_TURN_RIGHT:
ys = 90;
break;
}
return ys;
}
//=========================================================
// HandleAnimEvent - catches the monster-specific messages
// that occur when tagged animation frames are played.
//=========================================================
void CNPC_Houndeye::HandleAnimEvent( animevent_t *pEvent )
{
switch ( pEvent->event )
{
case HOUND_AE_WARN:
// do stuff for this event.
WarnSound();
break;
case HOUND_AE_STARTATTACK:
WarmUpSound();
break;
case HOUND_AE_HOPBACK:
{
float flGravity = GetCurrentGravity();
SetGroundEntity( NULL );
Vector forward;
AngleVectors( GetLocalAngles(), &forward );
Vector vecNewVelocity = forward * -200;
//jump up 36 inches
vecNewVelocity.z += sqrt( 2 * flGravity * 36 );
SetAbsVelocity( vecNewVelocity );
break;
}
case HOUND_AE_THUMP:
// emit the shockwaves
SonicAttack();
m_flNextAttack = gpGlobals->curtime + random->RandomFloat( 5.0, 8.0 );
break;
case HOUND_AE_ANGERSOUND1:
{
EmitSound( "NPC_Houndeye.Anger1" );
}
break;
case HOUND_AE_ANGERSOUND2:
{
EmitSound( "NPC_Houndeye.Anger2" );
}
break;
case HOUND_AE_CLOSE_EYE:
if ( !m_fDontBlink )
{
//<<TEMP>> pev->skin = HOUNDEYE_EYE_FRAMES - 1;
}
break;
case HOUND_AE_LEAP_HIT:
{
//<<TEMP>>return;//<<TEMP>>
SetGroundEntity( NULL );
//
// Take him off ground so engine doesn't instantly reset FL_ONGROUND.
//
UTIL_SetOrigin( this, GetLocalOrigin() + Vector( 0 , 0 , 1 ));
Vector vecJumpDir;
if ( GetEnemy() != NULL )
{
Vector vecEnemyEyePos = GetEnemy()->EyePosition();
float gravity = GetCurrentGravity();
if ( gravity <= 1 )
{
gravity = 1;
}
//
// How fast does the houndeye need to travel to reach my enemy's eyes given gravity?
//
float height = ( vecEnemyEyePos.z - GetAbsOrigin().z );
if ( height < 16 )
{
height = 16;
}
else if ( height > 120 )
{
height = 120;
}
float speed = sqrt( 2 * gravity * height );
float time = speed / gravity;
//
// Scale the sideways velocity to get there at the right time
//
vecJumpDir = vecEnemyEyePos - GetAbsOrigin();
vecJumpDir = vecJumpDir / time;
//
// Speed to offset gravity at the desired height.
//
vecJumpDir.z = speed;
//
// Don't jump too far/fast.
//
float distance = vecJumpDir.Length();
if ( distance > 650 )
{
vecJumpDir = vecJumpDir * ( 650.0 / distance );
}
}
else
{
Vector forward, up;
AngleVectors( GetLocalAngles(), &forward, NULL, &up );
//
// Jump hop, don't care where.
//
vecJumpDir = Vector( forward.x, forward.y, up.z ) * 350;
}
SetAbsVelocity( vecJumpDir );
m_flNextAttack = gpGlobals->curtime + 2;
break;
}
default:
BaseClass::HandleAnimEvent( pEvent );
break;
}
}
//=========================================================
// Spawn
//=========================================================
void CNPC_Houndeye::Spawn()
{
Precache( );
SetModel("models/houndeye.mdl");
SetHullType(HULL_WIDE_SHORT);
SetHullSizeNormal();
SetSolid( SOLID_BBOX );
AddSolidFlags( FSOLID_NOT_STANDABLE );
SetMoveType( MOVETYPE_STEP );
SetBloodColor( BLOOD_COLOR_YELLOW );
m_iHealth = sk_Houndeye_health.GetFloat();
m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result )
m_NPCState = NPC_STATE_NONE;
m_fAsleep = false; // everyone spawns awake
m_fDontBlink = false;
CapabilitiesAdd( bits_CAP_SQUAD );
CapabilitiesAdd( bits_CAP_MOVE_GROUND );
CapabilitiesAdd( bits_CAP_INNATE_RANGE_ATTACK1 );
CapabilitiesAdd( bits_CAP_TURN_HEAD );
m_flNextSecondaryAttack = 0;
m_bLoopClockwise = random->RandomInt(0,1) ? true : false;
m_pEnergyWave = NULL;
m_flEndEnergyWaveTime = 0;
SetCollisionGroup( HL2COLLISION_GROUP_HOUNDEYE );
NPCInit();
}
//=========================================================
// Precache - precaches all resources this monster needs
//=========================================================
void CNPC_Houndeye::Precache()
{
PrecacheModel("models/houndeye.mdl");
PrecacheScriptSound( "NPC_Houndeye.Anger1" );
PrecacheScriptSound( "NPC_Houndeye.Anger2" );
PrecacheScriptSound( "NPC_Houndeye.SpeakSentence" );
PrecacheScriptSound( "NPC_Houndeye.Idle" );
PrecacheScriptSound( "NPC_Houndeye.WarmUp" );
PrecacheScriptSound( "NPC_Houndeye.Warn" );
PrecacheScriptSound( "NPC_Houndeye.Alert" );
PrecacheScriptSound( "NPC_Houndeye.Die" );
PrecacheScriptSound( "NPC_Houndeye.Pain" );
PrecacheScriptSound( "NPC_Houndeye.Retreat" );
PrecacheScriptSound( "NPC_Houndeye.SonicAttack" );
PrecacheScriptSound( "NPC_Houndeye.GroupAttack" );
PrecacheScriptSound( "NPC_Houndeye.GroupFollow" );
UTIL_PrecacheOther("grenade_energy");
BaseClass::Precache();
}
//------------------------------------------------------------------------------
// Purpose :
// Input :
// Output :
//------------------------------------------------------------------------------
void CNPC_Houndeye::SpeakSentence( int sentenceType )
{
if (gpGlobals->curtime > m_flSoundWaitTime)
{
EmitSound( "NPC_Houndeye.SpeakSentence" );
m_flSoundWaitTime = gpGlobals->curtime + 1.0;
}
}
//=========================================================
// IdleSound
//=========================================================
void CNPC_Houndeye::IdleSound ( void )
{
if (!FOkToMakeSound())
{
return;
}
CPASAttenuationFilter filter( this,"NPC_Houndeye.Idle" );
EmitSound( filter, entindex(),"NPC_Houndeye.Idle" );
}
//=========================================================
// IdleSound
//=========================================================
void CNPC_Houndeye::WarmUpSound ( void )
{
EmitSound( "NPC_Houndeye.WarmUp" );
}
//=========================================================
// WarnSound
//=========================================================
void CNPC_Houndeye::WarnSound ( void )
{
EmitSound( "NPC_Houndeye.Warn" );
}
//=========================================================
// AlertSound
//=========================================================
void CNPC_Houndeye::AlertSound ( void )
{
// only first squad member makes ALERT sound.
if ( m_pSquad && !m_pSquad->IsLeader( this ) )
{
return;
}
EmitSound( "NPC_Houndeye.Alert" );
}
//=========================================================
// DeathSound
//=========================================================
void CNPC_Houndeye::DeathSound ( void )
{
EmitSound( "NPC_Houndeye.Die" );
}
//=========================================================
// PainSound
//=========================================================
void CNPC_Houndeye::PainSound ( void )
{
EmitSound( "NPC_Houndeye.Pain" );
}
//=========================================================
// WriteBeamColor - writes a color vector to the network
// based on the size of the group.
//=========================================================
void CNPC_Houndeye::WriteBeamColor ( void )
{
BYTE bRed, bGreen, bBlue;
if ( m_pSquad )
{
switch ( m_pSquad->NumMembers() )
{
case 2:
// no case for 0 or 1, cause those are impossible for monsters in Squads.
bRed = 101;
bGreen = 133;
bBlue = 221;
break;
case 3:
bRed = 67;
bGreen = 85;
bBlue = 255;
break;
case 4:
bRed = 62;
bGreen = 33;
bBlue = 211;
break;
default:
DevWarning( 2, "Unsupported Houndeye SquadSize!\n" );
bRed = 188;
bGreen = 220;
bBlue = 255;
break;
}
}
else
{
// solo houndeye - weakest beam
bRed = 188;
bGreen = 220;
bBlue = 255;
}
WRITE_BYTE( bRed );
WRITE_BYTE( bGreen );
WRITE_BYTE( bBlue );
}
//-----------------------------------------------------------------------------
// Purpose: Plays the engine sound.
//-----------------------------------------------------------------------------
void CNPC_Houndeye::NPCThink(void)
{
if (m_pEnergyWave)
{
if (gpGlobals->curtime > m_flEndEnergyWaveTime)
{
UTIL_Remove(m_pEnergyWave);
m_pEnergyWave = NULL;
}
}
// -----------------------------------------------------
// Update collision group
// While I'm running I'm allowed to penetrate
// other houndeyes
// -----------------------------------------------------
Vector vVelocity;
GetVelocity( &vVelocity, NULL );
if (vVelocity.Length() > 10)
{
SetCollisionGroup( HL2COLLISION_GROUP_HOUNDEYE );
}
else
{
// Don't go solid if resting in another houndeye
trace_t tr;
AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,1),
GetHullMins(), GetHullMaxs(),
MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
if (!tr.startsolid)
{
SetCollisionGroup( COLLISION_GROUP_NONE );
}
else
{
SetCollisionGroup( HL2COLLISION_GROUP_HOUNDEYE );
}
}
/*
if (GetCollisionGroup() == HL2COLLISION_GROUP_HOUNDEYE)
{
NDebugOverlay::Box(GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 0, 255, 0, 0, 0);
}
else
{
NDebugOverlay::Box(GetAbsOrigin(), GetHullMins(), GetHullMaxs(), 255, 0, 0, 0, 0);
}
*/
BaseClass::NPCThink();
}
//------------------------------------------------------------------------------
// Purpose : Broadcast retreat occasionally when hurt
// Input :
// Output :
//------------------------------------------------------------------------------
int CNPC_Houndeye::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
if (m_pSquad && random->RandomInt(0,10) == 10)
{
EmitSound( "NPC_Houndeye.Retreat" );
m_flSoundWaitTime = gpGlobals->curtime + 1.0;
m_pSquad->BroadcastInteraction( g_interactionHoundeyeGroupRetreat, NULL, this );
}
return BaseClass::OnTakeDamage_Alive( info );
}
//------------------------------------------------------------------------------
// Purpose : Broadcast retreat when member of squad killed
// Input :
// Output :
//------------------------------------------------------------------------------
void CNPC_Houndeye::Event_Killed( const CTakeDamageInfo &info )
{
EmitSound( "NPC_Houndeye.Retreat" );
m_flSoundWaitTime = gpGlobals->curtime + 1.0;
if (m_pSquad)
{
m_pSquad->BroadcastInteraction( g_interactionHoundeyeGroupRetreat, NULL, this );
}
BaseClass::Event_Killed( info );
}
//=========================================================
// SonicAttack
//=========================================================
void CNPC_Houndeye::SonicAttack ( void )
{
EmitSound( "NPC_Houndeye.SonicAttack" );
if (m_pEnergyWave)
{
UTIL_Remove(m_pEnergyWave);
}
Vector vFacingDir = EyeDirection3D( );
m_pEnergyWave = (CEnergyWave*)Create( "energy_wave", EyePosition(), GetLocalAngles() );
m_flEndEnergyWaveTime = gpGlobals->curtime + 1; //<<TEMP>> magic
m_pEnergyWave->SetAbsVelocity( 100*vFacingDir );
CBaseEntity *pEntity = NULL;
// iterate on all entities in the vicinity.
for ( CEntitySphereQuery sphere( GetAbsOrigin(), HOUNDEYE_MAX_ATTACK_RADIUS ); pEntity = sphere.GetCurrentEntity(); sphere.NextEntity() )
{
if (pEntity->Classify() == CLASS_HOUNDEYE)
{
continue;
}
if (pEntity->GetFlags() & FL_NOTARGET)
{
continue;
}
IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
if ( pEntity->m_takedamage != DAMAGE_NO || pPhysicsObject)
{
// --------------------------
// Adjust damage by distance
// --------------------------
float flDist = (pEntity->WorldSpaceCenter() - GetAbsOrigin()).Length();
float flDamageAdjuster = 1-( flDist / HOUNDEYE_MAX_ATTACK_RADIUS );
// --------------------------
// Adjust damage by direction
// --------------------------
Vector forward;
AngleVectors( GetAbsAngles(), &forward );
Vector vEntDir = (pEntity->GetAbsOrigin() - GetAbsOrigin());
VectorNormalize(vEntDir);
float flDotPr = DotProduct(forward,vEntDir);
flDamageAdjuster *= flDotPr;
if (flDamageAdjuster < 0)
{
continue;
}
// --------------------------
// Adjust damage by visibility
// --------------------------
if ( !FVisible( pEntity ) )
{
if ( pEntity->IsPlayer() )
{
// if this entity is a client, and is not in full view, inflict half damage. We do this so that players still
// take the residual damage if they don't totally leave the houndeye's effective radius. We restrict it to clients
// so that monsters in other parts of the level don't take the damage and get pissed.
flDamageAdjuster *= 0.5;
}
else if ( !FClassnameIs( pEntity, "func_breakable" ) && !FClassnameIs( pEntity, "func_pushable" ) )
{
// do not hurt nonclients through walls, but allow damage to be done to breakables
continue;
}
}
// ------------------------------
// Apply the damage
// ------------------------------
if (pEntity->m_takedamage != DAMAGE_NO)
{
CTakeDamageInfo info( this, this, flDamageAdjuster * sk_Houndeye_dmg_blast.GetFloat(), DMG_SONIC | DMG_ALWAYSGIB );
CalculateExplosiveDamageForce( &info, (pEntity->GetAbsOrigin() - GetAbsOrigin()), pEntity->GetAbsOrigin() );
pEntity->TakeDamage( info );
// Throw the player
if ( pEntity->IsPlayer() )
{
Vector forward;
AngleVectors( GetLocalAngles(), &forward );
Vector vecVelocity = pEntity->GetAbsVelocity();
vecVelocity += forward * 250 * flDamageAdjuster;
vecVelocity.z = 300 * flDamageAdjuster;
pEntity->SetAbsVelocity( vecVelocity );
pEntity->ViewPunch( QAngle(random->RandomInt(-20,20), 0, random->RandomInt(-20,20)) );
}
}
// ------------------------------
// Apply physics foces
// ------------------------------
IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
if (pPhysicsObject)
{
float flForce = flDamageAdjuster * 8000;
pPhysicsObject->ApplyForceCenter( (vEntDir+Vector(0,0,0.2)) * flForce );
pPhysicsObject->ApplyTorqueCenter( vEntDir * flForce );
}
}
}
}
//=========================================================
// start task
//=========================================================
void CNPC_Houndeye::StartTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_HOUND_GET_PATH_TO_CIRCLE:
{
if (GetEnemy() == NULL)
{
TaskFail(FAIL_NO_ENEMY);
}
else
{
Vector vTargetPos = GetEnemyLKP();
vTargetPos.z = GetFloorZ(vTargetPos);
if (GetNavigator()->SetRadialGoal(vTargetPos, random->RandomInt(50,500), 90, 175, m_bLoopClockwise))
{
TaskComplete();
return;
}
TaskFail(FAIL_NO_ROUTE);
}
break;
}
case TASK_HOUND_REVERSE_STRAFE_DIR:
{
// Try the other direction
m_bLoopClockwise = (m_bLoopClockwise) ? false : true;
TaskComplete();
break;
}
// Override to set appropriate distances
case TASK_GET_PATH_TO_ENEMY_LOS:
{
float flMaxRange = HOUNDEYE_MAX_ATTACK_RADIUS * 0.9;
float flMinRange = HOUNDEYE_MIN_ATTACK_RADIUS;
Vector posLos;
bool foundLos = false;
if (GetEnemy() != NULL)
{
foundLos = GetTacticalServices()->FindLos(GetEnemyLKP(),GetEnemy()->EyePosition(), flMinRange, flMaxRange, 0.0, &posLos);
}
else
{
TaskFail(FAIL_NO_TARGET);
return;
}
if (foundLos)
{
GetNavigator()->SetGoal( AI_NavGoal_t( posLos, ACT_RUN, AIN_HULL_TOLERANCE ) );
}
else
{
TaskFail(FAIL_NO_SHOOT);
}
break;
}
case TASK_HOUND_FALL_ASLEEP:
{
m_fAsleep = true; // signal that hound is lying down (must stand again before doing anything else!)
TaskComplete( true );
break;
}
case TASK_HOUND_WAKE_UP:
{
m_fAsleep = false; // signal that hound is standing again
TaskComplete( true );
break;
}
case TASK_HOUND_OPEN_EYE:
{
m_fDontBlink = false; // turn blinking back on and that code will automatically open the eye
TaskComplete( true );
break;
}
case TASK_HOUND_CLOSE_EYE:
{
//<<TEMP>> pev->skin = 0;
m_fDontBlink = true; // tell blink code to leave the eye alone.
break;
}
case TASK_HOUND_THREAT_DISPLAY:
{
SetIdealActivity( ACT_IDLE_ANGRY );
break;
}
case TASK_HOUND_HOP_BACK:
{
SetIdealActivity( ACT_LEAP );
break;
}
case TASK_RANGE_ATTACK1:
{
SetIdealActivity( ACT_RANGE_ATTACK1 );
break;
}
default:
{
BaseClass::StartTask(pTask);
break;
}
}
}
//=========================================================
// RunTask
//=========================================================
void CNPC_Houndeye::RunTask( const Task_t *pTask )
{
switch ( pTask->iTask )
{
case TASK_HOUND_THREAT_DISPLAY:
{
GetMotor()->SetIdealYawToTargetAndUpdate( GetEnemyLKP(), AI_KEEP_YAW_SPEED );
if ( IsActivityFinished() )
{
TaskComplete();
}
break;
}
case TASK_HOUND_CLOSE_EYE:
{
/*//<<TEMP>>
if ( pev->skin < HOUNDEYE_EYE_FRAMES - 1 )
{
pev->skin++;
}
*/
break;
}
case TASK_HOUND_HOP_BACK:
{
if ( IsActivityFinished() )
{
TaskComplete();
}
break;
}
default:
{
BaseClass::RunTask(pTask);
break;
}
}
}
//------------------------------------------------------------------------------
// Purpose :
// Input :
// Output :
//------------------------------------------------------------------------------
void CNPC_Houndeye::PrescheduleThink ( void )
{
BaseClass::PrescheduleThink();
// if the hound is mad and is running, make hunt noises.
if ( m_NPCState == NPC_STATE_COMBAT && ( GetActivity() == ACT_RUN ) && random->RandomFloat( 0, 1 ) < 0.2 )
{
WarnSound();
}
// at random, initiate a blink if not already blinking or sleeping
if ( !m_fDontBlink )
{
/*//<<TEMP>>//<<TEMP>>
if ( ( pev->skin == 0 ) && random->RandomInt(0,0x7F) == 0 )
{// start blinking!
pev->skin = HOUNDEYE_EYE_FRAMES - 1;
}
else if ( pev->skin != 0 )
{// already blinking
pev->skin--;
}
*/
}
}
//-----------------------------------------------------------------------------
// Purpose: Override base class activiites
// Input :
// Output :
//-----------------------------------------------------------------------------
Activity CNPC_Houndeye::NPC_TranslateActivity( Activity eNewActivity )
{
if ( eNewActivity == ACT_IDLE && m_NPCState == NPC_STATE_COMBAT )
{
return ACT_IDLE_ANGRY;
}
return eNewActivity;
}
//------------------------------------------------------------------------------
// Purpose :
// Input :
// Output :
//------------------------------------------------------------------------------
int CNPC_Houndeye::TranslateSchedule( int scheduleType )
{
switch( scheduleType )
{
case SCHED_IDLE_STAND:
{
return BaseClass::TranslateSchedule( scheduleType );
}
case SCHED_RANGE_ATTACK1:
{
return SCHED_HOUND_RANGE_ATTACK1;
}
case SCHED_CHASE_ENEMY_FAILED:
{
return SCHED_COMBAT_FACE;
}
default:
{
return BaseClass::TranslateSchedule ( scheduleType );
}
}
}
//------------------------------------------------------------------------------
// Purpose : Is anyone in the squad currently attacking
// Input :
// Output :
//------------------------------------------------------------------------------
bool CNPC_Houndeye::IsAnyoneInSquadAttacking( void )
{
if (!m_pSquad)
{
return false;
}
AISquadIter_t iter;
for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
{
if (pSquadMember->IsCurSchedule(SCHED_HOUND_RANGE_ATTACK1))
{
return true;
}
}
return false;
}
//=========================================================
// SelectSchedule
//=========================================================
int CNPC_Houndeye::SelectSchedule( void )
{
switch ( m_NPCState )
{
case NPC_STATE_IDLE:
case NPC_STATE_ALERT:
{
if ( HasCondition(COND_LIGHT_DAMAGE) ||
HasCondition(COND_HEAVY_DAMAGE) )
{
return SCHED_TAKE_COVER_FROM_ORIGIN;
}
break;
}
case NPC_STATE_COMBAT:
{
// dead enemy
if ( HasCondition( COND_ENEMY_DEAD ) )
{
// call base class, all code to handle dead enemies is centralized there.
return BaseClass::SelectSchedule();
}
// If a group attack was requested attack even if attack conditions not met
if ( HasCondition( COND_HOUND_GROUP_ATTACK ))
{
// Check that I'm not standing in another hound eye
// before attacking
trace_t tr;
AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + Vector(0,0,1),
GetHullMins(), GetHullMaxs(),
MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
if (!tr.startsolid)
{
return SCHED_HOUND_GROUP_ATTACK;
}
// Otherwise attack as soon as I can
else
{
m_flNextAttack = gpGlobals->curtime;
SCHED_HOUND_ATTACK_STRAFE;
}
}
// If a group retread was requested
if ( HasCondition( COND_HOUND_GROUP_RETREAT ))
{
return SCHED_HOUND_GROUP_RETREAT;
}
if ( HasCondition( COND_LIGHT_DAMAGE ) |
HasCondition( COND_HEAVY_DAMAGE ) )
{
if ( random->RandomFloat( 0 , 1 ) <= 0.4 )
{
trace_t tr;
Vector forward;
AngleVectors( GetAbsAngles(), &forward );
AI_TraceHull( GetAbsOrigin(), GetAbsOrigin() + forward * -128,
GetHullMins(), GetHullMaxs(),
MASK_NPCSOLID, this, COLLISION_GROUP_NONE, &tr );
if ( tr.fraction == 1.0 )
{
// it's clear behind, so the hound will jump
return SCHED_HOUND_HOP_RETREAT;
}
}
return SCHED_TAKE_COVER_FROM_ENEMY;
}
// If a group rally was requested
if ( HasCondition( COND_HOUND_GROUP_RALLEY ))
{
return SCHED_HOUND_GROUP_RALLEY;
}
if ( HasCondition( COND_CAN_RANGE_ATTACK1 ) )
{
if (m_pSquad && random->RandomInt(0,4) == 0)
{
if (!IsAnyoneInSquadAttacking())
{
EmitSound( "NPC_Houndeye.GroupAttack" );
m_flSoundWaitTime = gpGlobals->curtime + 1.0;
m_pSquad->BroadcastInteraction( g_interactionHoundeyeGroupAttack, NULL, this );
return SCHED_HOUND_GROUP_ATTACK;
}
}
//<<TEMP>>comment
SetCollisionGroup( COLLISION_GROUP_NONE );
return SCHED_RANGE_ATTACK1;
}
else
{
if (m_pSquad && random->RandomInt(0,5) == 0)
{
if (!IsAnyoneInSquadAttacking())
{
EmitSound( "NPC_Houndeye.GroupFollow" );
m_flSoundWaitTime = gpGlobals->curtime + 1.0;
m_pSquad->BroadcastInteraction( g_interactionHoundeyeGroupRalley, NULL, this );
return SCHED_HOUND_ATTACK_STRAFE;
}
}
return SCHED_HOUND_ATTACK_STRAFE;
}
break;
}
}
return BaseClass::SelectSchedule();
}
//-----------------------------------------------------------------------------
// Purpose: This is a generic function (to be implemented by sub-classes) to
// handle specific interactions between different types of characters
// (For example the barnacle grabbing an NPC)
// Input : Constant for the type of interaction
// Output : true - if sub-class has a response for the interaction
// false - if sub-class has no response
//-----------------------------------------------------------------------------
bool CNPC_Houndeye::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
{
if (interactionType == g_interactionHoundeyeGroupAttack)
{
SetCondition(COND_HOUND_GROUP_ATTACK);
return true;
}
else if (interactionType == g_interactionHoundeyeGroupRetreat)
{
SetCondition(COND_HOUND_GROUP_RETREAT);
return true;
}
else if (interactionType == g_interactionHoundeyeGroupRalley)
{
SetCondition(COND_HOUND_GROUP_RALLEY);
SetTarget(sourceEnt);
m_bLoopClockwise = false;
return true;
}
return false;
}
//-----------------------------------------------------------------------------
//
// Schedules
//
//-----------------------------------------------------------------------------
//=========================================================
// SCHED_HOUND_ATTACK_STRAFE
//
// Run a cirle around my enemy
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_HOUND_ATTACK_STRAFE ,
" Tasks "
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HOUND_ATTACK_STRAFE_REVERSE"
" TASK_HOUND_GET_PATH_TO_CIRCLE 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
""
" Interrupts "
" COND_WEAPON_SIGHT_OCCLUDED"
" COND_NEW_ENEMY"
" COND_ENEMY_DEAD"
" COND_HEAVY_DAMAGE"
" COND_CAN_RANGE_ATTACK1"
" COND_HOUND_GROUP_ATTACK"
" COND_HOUND_GROUP_RETREAT"
);
//=========================================================
// SCHED_HOUND_ATTACK_STRAFE_REVERSE
//
// Run a cirle around my enemy
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_HOUND_ATTACK_STRAFE_REVERSE ,
" Tasks "
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HOUND_CHASE_ENEMY"
" TASK_HOUND_REVERSE_STRAFE_DIR 0"
" TASK_HOUND_GET_PATH_TO_CIRCLE 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
""
" Interrupts "
" COND_WEAPON_SIGHT_OCCLUDED"
" COND_NEW_ENEMY"
" COND_ENEMY_DEAD"
" COND_HEAVY_DAMAGE"
" COND_CAN_RANGE_ATTACK1"
" COND_HOUND_GROUP_ATTACK"
" COND_HOUND_GROUP_RETREAT"
);
//========================================================
// SCHED_HOUND_CHASE_ENEMY
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_HOUND_CHASE_ENEMY,
" Tasks"
" TASK_SET_TOLERANCE_DISTANCE 30 "
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_CHASE_ENEMY_FAILED"
" TASK_GET_PATH_TO_ENEMY 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_ENEMY_DEAD"
" COND_CAN_RANGE_ATTACK1"
" COND_HOUND_GROUP_ATTACK"
" COND_HOUND_GROUP_RETREAT"
);
//=========================================================
// SCHED_HOUND_GROUP_ATTACK
//
// Face enemy, pause, then attack
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_HOUND_GROUP_ATTACK ,
" Tasks "
" TASK_STOP_MOVING 0"
" TASK_FACE_ENEMY 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE_ANGRY"
" TASK_SPEAK_SENTENCE 0"
" TASK_WAIT 1"
" TASK_SET_SCHEDULE SCHEDULE:SCHED_HOUND_RANGE_ATTACK1"
""
" Interrupts "
" COND_NEW_ENEMY"
" COND_ENEMY_DEAD"
" COND_HEAVY_DAMAGE"
);
//=========================================================
// > SCHED_HOUND_GROUP_RETREAT
//
// Take cover from enemy!
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_HOUND_GROUP_RETREAT,
" Tasks"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HOUND_ATTACK_STRAFE"
" TASK_STOP_MOVING 0"
" TASK_WAIT 0.2"
" TASK_SET_TOLERANCE_DISTANCE 24"
" TASK_FIND_COVER_FROM_ENEMY 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
" TASK_REMEMBER MEMORY:INCOVER"
" TASK_FACE_ENEMY 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE" // Translated to cover
" TASK_SET_SCHEDULE SCHEDULE:SCHED_HOUND_COVER_WAIT"
""
" Interrupts"
" COND_NEW_ENEMY"
);
//=========================================================
// > SCHED_HOUND_GROUP_RALLEY
//
// Run to rally hound!
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_HOUND_GROUP_RALLEY,
" Tasks"
" TASK_SET_TOLERANCE_DISTANCE 30"
" TASK_SET_FAIL_SCHEDULE SCHEDULE:SCHED_HOUND_ATTACK_STRAFE"
" TASK_GET_PATH_TO_TARGET 0"
" TASK_RUN_PATH 0"
" TASK_WAIT_FOR_MOVEMENT 0"
""
" Interrupts"
" COND_NEW_ENEMY"
" COND_ENEMY_DEAD"
" COND_HEAVY_DAMAGE"
" COND_HOUND_GROUP_ATTACK"
" COND_HOUND_GROUP_RETREAT"
);
//=========================================================
// > SCHED_HOUND_COVER_WAIT
//
// Wait in cover till enemy see's me or I take damage
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_HOUND_COVER_WAIT,
" Tasks"
" TASK_WAIT 2"
""
" Interrupts"
" COND_SEE_ENEMY"
" COND_LIGHT_DAMAGE"
);
//=========================================================
// > SCHED_HOUND_RANGE_ATTACK1
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_HOUND_RANGE_ATTACK1,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_SET_ACTIVITY ACTIVITY:ACT_IDLE_ANGRY"
" TASK_FACE_IDEAL 0"
" TASK_RANGE_ATTACK1 0"
""
" Interrupts"
//" COND_LIGHT_DAMAGE" // don't interupt on small damage
" COND_HEAVY_DAMAGE"
);
//=========================================================
// > SCHED_HOUND_HOP_RETREAT
//=========================================================
AI_DEFINE_SCHEDULE
(
SCHED_HOUND_HOP_RETREAT,
" Tasks"
" TASK_STOP_MOVING 0"
" TASK_HOUND_HOP_BACK 0"
" TASK_SET_SCHEDULE SCHEDULE:SCHED_TAKE_COVER_FROM_ENEMY"
""
" Interrupts"
);