diff options
Diffstat (limited to 'game/server/hl2/npc_houndeye.cpp')
| -rw-r--r-- | game/server/hl2/npc_houndeye.cpp | 1395 |
1 files changed, 1395 insertions, 0 deletions
diff --git a/game/server/hl2/npc_houndeye.cpp b/game/server/hl2/npc_houndeye.cpp new file mode 100644 index 0000000..c121d11 --- /dev/null +++ b/game/server/hl2/npc_houndeye.cpp @@ -0,0 +1,1395 @@ +//========= 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" +); |