summaryrefslogtreecommitdiff
path: root/game/server/hl1/hl1_npc_houndeye.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/hl1/hl1_npc_houndeye.cpp')
-rw-r--r--game/server/hl1/hl1_npc_houndeye.cpp1287
1 files changed, 1287 insertions, 0 deletions
diff --git a/game/server/hl1/hl1_npc_houndeye.cpp b/game/server/hl1/hl1_npc_houndeye.cpp
new file mode 100644
index 0000000..10ae189
--- /dev/null
+++ b/game/server/hl1/hl1_npc_houndeye.cpp
@@ -0,0 +1,1287 @@
+//========= 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()