diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/hl1/hl1_npc_houndeye.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/hl1/hl1_npc_houndeye.cpp')
| -rw-r--r-- | game/server/hl1/hl1_npc_houndeye.cpp | 1287 |
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() |