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