/*** * * Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * * This source code contains proprietary and confidential information of * Valve LLC and its suppliers. Access to this code is restricted to * persons who have executed a written SDK license with Valve. Any access, * use or distribution of this code by or to any unlicensed person is illegal. * ****/ //========================================================= // Houndeye - spooky sonic dog. //========================================================= #include "extdll.h" #include "util.h" #include "cbase.h" #include "monsters.h" #include "schedule.h" #include "animation.h" #include "nodes.h" #include "squadmonster.h" #include "soundent.h" #include "game.h" extern CGraph WorldGraph; // 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_MAX_ATTACK_RADIUS 384 #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 //========================================================= // monster-specific tasks //========================================================= enum { TASK_HOUND_CLOSE_EYE = LAST_COMMON_TASK + 1, 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_COMMON_SCHEDULE + 1, SCHED_HOUND_HOP_RETREAT, SCHED_HOUND_FAIL }; //========================================================= // 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 class CHoundeye : public CSquadMonster { public: void Spawn( void ); void Precache( void ); int Classify ( void ); void HandleAnimEvent( MonsterEvent_t *pEvent ); void SetYawSpeed ( void ); void WarmUpSound ( void ); void AlertSound( void ); void DeathSound( void ); void WarnSound( void ); void PainSound( void ); void IdleSound( void ); void StartTask( Task_t *pTask ); void RunTask ( Task_t *pTask ); void SonicAttack( void ); void PrescheduleThink( void ); void SetActivity ( Activity NewActivity ); void WriteBeamColor ( void ); BOOL CheckRangeAttack1 ( float flDot, float flDist ); BOOL FValidateHintType ( short sHint ); BOOL FCanActiveIdle ( void ); Schedule_t *GetScheduleOfType ( int Type ); Schedule_t *GetSchedule( void ); int Save( CSave &save ); int Restore( CRestore &restore ); CUSTOM_SCHEDULES static TYPEDESCRIPTION m_SaveData[]; int m_iSpriteTexture; BOOL m_fAsleep;// some houndeyes sleep in idle mode if this is set, the houndeye is lying down BOOL m_fDontBlink;// don't try to open/close eye if this bit is set! Vector m_vecPackCenter; // the center of the pack. The leader maintains this by averaging the origins of all pack members. }; LINK_ENTITY_TO_CLASS( monster_houndeye, CHoundeye ) TYPEDESCRIPTION CHoundeye::m_SaveData[] = { DEFINE_FIELD( CHoundeye, m_iSpriteTexture, FIELD_INTEGER ), DEFINE_FIELD( CHoundeye, m_fAsleep, FIELD_BOOLEAN ), DEFINE_FIELD( CHoundeye, m_fDontBlink, FIELD_BOOLEAN ), DEFINE_FIELD( CHoundeye, m_vecPackCenter, FIELD_POSITION_VECTOR ), }; IMPLEMENT_SAVERESTORE( CHoundeye, CSquadMonster ) //========================================================= // Classify - indicates this monster's place in the // relationship table. //========================================================= int CHoundeye :: Classify ( void ) { return CLASS_ALIEN_MONSTER; } //========================================================= // FValidateHintType //========================================================= BOOL CHoundeye :: FValidateHintType ( short sHint ) { int i; static short sHoundHints[] = { HINT_WORLD_MACHINERY, HINT_WORLD_BLINKING_LIGHT, HINT_WORLD_HUMAN_BLOOD, HINT_WORLD_ALIEN_BLOOD, }; for ( i = 0 ; i < ARRAYSIZE ( sHoundHints ) ; i++ ) { if ( sHoundHints[ i ] == sHint ) { return TRUE; } } ALERT ( at_aiconsole, "Couldn't validate hint type" ); return FALSE; } //========================================================= // FCanActiveIdle //========================================================= BOOL CHoundeye :: FCanActiveIdle ( void ) { if ( InSquad() ) { CSquadMonster *pSquadLeader = MySquadLeader(); for (int i = 0; i < MAX_SQUAD_MEMBERS;i++) { CSquadMonster *pMember = pSquadLeader->MySquadMember(i); if ( pMember != NULL && pMember != this && pMember->m_iHintNode != NO_NODE ) { // someone else in the group is active idling right now! return FALSE; } } return TRUE; } return TRUE; } //========================================================= // CheckRangeAttack1 - overridden for houndeyes so that they // try to get within half of their max attack radius before // attacking, so as to increase their chances of doing damage. //========================================================= BOOL CHoundeye :: CheckRangeAttack1 ( float flDot, float flDist ) { if ( flDist <= ( HOUNDEYE_MAX_ATTACK_RADIUS * 0.5 ) && flDot >= 0.3 ) { return TRUE; } return FALSE; } //========================================================= // SetYawSpeed - allows each sequence to have a different // turn rate associated with it. //========================================================= void CHoundeye :: SetYawSpeed ( void ) { int ys; ys = 90; switch ( m_Activity ) { case ACT_CROUCHIDLE://sleeping! ys = 0; break; case ACT_IDLE: ys = 60; break; case ACT_WALK: case ACT_RUN: case ACT_TURN_LEFT: case ACT_TURN_RIGHT: break; default: break; } pev->yaw_speed = ys; } //========================================================= // SetActivity //========================================================= void CHoundeye :: SetActivity ( Activity NewActivity ) { int iSequence; if ( NewActivity == m_Activity ) return; if ( m_MonsterState == MONSTERSTATE_COMBAT && NewActivity == ACT_IDLE && RANDOM_LONG(0,1) ) { // play pissed idle. iSequence = LookupSequence( "madidle" ); m_Activity = 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 m_IdealActivity = m_Activity; // Set to the desired anim, or default anim if the desired is not present if ( iSequence > ACTIVITY_NOT_AVAILABLE ) { pev->sequence = iSequence; // Set to the reset anim (if it's there) pev->frame = 0; // FIX: frame counter shouldn't be reset when its the same activity as before ResetSequenceInfo(); SetYawSpeed(); } } else { CSquadMonster :: SetActivity ( NewActivity ); } } //========================================================= // HandleAnimEvent - catches the monster-specific messages // that occur when tagged animation frames are played. //========================================================= void CHoundeye :: HandleAnimEvent( MonsterEvent_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 = g_psv_gravity->value; pev->flags &= ~FL_ONGROUND; pev->velocity = gpGlobals->v_forward * -200; pev->velocity.z += (0.6 * flGravity) * 0.5; break; } case HOUND_AE_THUMP: // emit the shockwaves SonicAttack(); break; case HOUND_AE_ANGERSOUND1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM); break; case HOUND_AE_ANGERSOUND2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "houndeye/he_pain1.wav", 1, ATTN_NORM); break; case HOUND_AE_CLOSE_EYE: if ( !m_fDontBlink ) { pev->skin = HOUNDEYE_EYE_FRAMES - 1; } break; default: CSquadMonster::HandleAnimEvent( pEvent ); break; } } //========================================================= // Spawn //========================================================= void CHoundeye :: Spawn() { Precache( ); SET_MODEL(ENT(pev), "models/houndeye.mdl"); UTIL_SetSize(pev, Vector ( -16, -16, 0 ), Vector ( 16, 16, 36 ) ); pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_STEP; m_bloodColor = BLOOD_COLOR_YELLOW; pev->effects = 0; pev->health = gSkillData.houndeyeHealth; pev->yaw_speed = 5;//!!! should we put this in the monster's changeanim function since turn rates may vary with state/anim? m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) m_MonsterState = MONSTERSTATE_NONE; m_fAsleep = FALSE; // everyone spawns awake m_fDontBlink = FALSE; m_afCapability |= bits_CAP_SQUAD; MonsterInit(); } //========================================================= // Precache - precaches all resources this monster needs //========================================================= void CHoundeye :: Precache() { PRECACHE_MODEL("models/houndeye.mdl"); PRECACHE_SOUND("houndeye/he_alert1.wav"); PRECACHE_SOUND("houndeye/he_alert2.wav"); PRECACHE_SOUND("houndeye/he_alert3.wav"); PRECACHE_SOUND("houndeye/he_die1.wav"); PRECACHE_SOUND("houndeye/he_die2.wav"); PRECACHE_SOUND("houndeye/he_die3.wav"); PRECACHE_SOUND("houndeye/he_idle1.wav"); PRECACHE_SOUND("houndeye/he_idle2.wav"); PRECACHE_SOUND("houndeye/he_idle3.wav"); PRECACHE_SOUND("houndeye/he_hunt1.wav"); PRECACHE_SOUND("houndeye/he_hunt2.wav"); PRECACHE_SOUND("houndeye/he_hunt3.wav"); PRECACHE_SOUND("houndeye/he_pain1.wav"); PRECACHE_SOUND("houndeye/he_pain3.wav"); PRECACHE_SOUND("houndeye/he_pain4.wav"); PRECACHE_SOUND("houndeye/he_pain5.wav"); PRECACHE_SOUND("houndeye/he_attack1.wav"); PRECACHE_SOUND("houndeye/he_attack3.wav"); PRECACHE_SOUND("houndeye/he_blast1.wav"); PRECACHE_SOUND("houndeye/he_blast2.wav"); PRECACHE_SOUND("houndeye/he_blast3.wav"); m_iSpriteTexture = PRECACHE_MODEL( "sprites/shockwave.spr" ); } //========================================================= // IdleSound //========================================================= void CHoundeye :: IdleSound ( void ) { switch ( RANDOM_LONG(0,2) ) { case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle1.wav", 1, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle2.wav", 1, ATTN_NORM ); break; case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle3.wav", 1, ATTN_NORM ); break; } } //========================================================= // IdleSound //========================================================= void CHoundeye :: WarmUpSound ( void ) { switch ( RANDOM_LONG(0,1) ) { case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "houndeye/he_attack1.wav", 0.7, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "houndeye/he_attack3.wav", 0.7, ATTN_NORM ); break; } } //========================================================= // WarnSound //========================================================= void CHoundeye :: WarnSound ( void ) { switch ( RANDOM_LONG(0,2) ) { case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt1.wav", 1, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt2.wav", 1, ATTN_NORM ); break; case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt3.wav", 1, ATTN_NORM ); break; } } //========================================================= // AlertSound //========================================================= void CHoundeye :: AlertSound ( void ) { if ( InSquad() && !IsLeader() ) { return; // only leader makes ALERT sound. } switch ( RANDOM_LONG(0,2) ) { case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert1.wav", 1, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert2.wav", 1, ATTN_NORM ); break; case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert3.wav", 1, ATTN_NORM ); break; } } //========================================================= // DeathSound //========================================================= void CHoundeye :: DeathSound ( void ) { switch ( RANDOM_LONG(0,2) ) { case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die1.wav", 1, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die2.wav", 1, ATTN_NORM ); break; case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die3.wav", 1, ATTN_NORM ); break; } } //========================================================= // PainSound //========================================================= void CHoundeye :: PainSound ( void ) { switch ( RANDOM_LONG(0,2) ) { case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain4.wav", 1, ATTN_NORM ); break; case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain5.wav", 1, ATTN_NORM ); break; } } //========================================================= // WriteBeamColor - writes a color vector to the network // based on the size of the group. //========================================================= void CHoundeye :: WriteBeamColor ( void ) { BYTE bRed, bGreen, bBlue; if ( InSquad() ) { switch ( SquadCount() ) { 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: ALERT ( at_aiconsole, "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 ); } //========================================================= // SonicAttack //========================================================= void CHoundeye :: SonicAttack ( void ) { float flAdjustedDamage; float flDist; switch ( RANDOM_LONG( 0, 2 ) ) { case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast1.wav", 1, ATTN_NORM); break; case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast2.wav", 1, ATTN_NORM); break; case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast3.wav", 1, ATTN_NORM); break; } // blast circles MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); WRITE_BYTE( TE_BEAMCYLINDER ); WRITE_COORD( pev->origin.x); WRITE_COORD( pev->origin.y); WRITE_COORD( pev->origin.z + 16); WRITE_COORD( pev->origin.x); WRITE_COORD( pev->origin.y); WRITE_COORD( pev->origin.z + 16 + HOUNDEYE_MAX_ATTACK_RADIUS / .2); // reach damage radius over .3 seconds WRITE_SHORT( m_iSpriteTexture ); WRITE_BYTE( 0 ); // startframe WRITE_BYTE( 0 ); // framerate WRITE_BYTE( 2 ); // life WRITE_BYTE( 16 ); // width WRITE_BYTE( 0 ); // noise WriteBeamColor(); WRITE_BYTE( 255 ); //brightness WRITE_BYTE( 0 ); // speed MESSAGE_END(); MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); WRITE_BYTE( TE_BEAMCYLINDER ); WRITE_COORD( pev->origin.x); WRITE_COORD( pev->origin.y); WRITE_COORD( pev->origin.z + 16); WRITE_COORD( pev->origin.x); WRITE_COORD( pev->origin.y); WRITE_COORD( pev->origin.z + 16 + ( HOUNDEYE_MAX_ATTACK_RADIUS / 2 ) / .2); // reach damage radius over .3 seconds WRITE_SHORT( m_iSpriteTexture ); WRITE_BYTE( 0 ); // startframe WRITE_BYTE( 0 ); // framerate WRITE_BYTE( 2 ); // life WRITE_BYTE( 16 ); // width WRITE_BYTE( 0 ); // noise WriteBeamColor(); WRITE_BYTE( 255 ); //brightness WRITE_BYTE( 0 ); // speed MESSAGE_END(); CBaseEntity *pEntity = NULL; // iterate on all entities in the vicinity. while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, HOUNDEYE_MAX_ATTACK_RADIUS )) != NULL) { if ( pEntity->pev->takedamage != DAMAGE_NO ) { if ( !FClassnameIs(pEntity->pev, "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 ( SquadCount() > 1 ) { // squad gets attack bonus. flAdjustedDamage = gSkillData.houndeyeDmgBlast + gSkillData.houndeyeDmgBlast * ( HOUNDEYE_SQUAD_BONUS * ( SquadCount() - 1 ) ); } else { // solo flAdjustedDamage = gSkillData.houndeyeDmgBlast; } flDist = (pEntity->Center() - pev->origin).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->pev, "func_breakable" ) && !FClassnameIs( pEntity->pev, "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 ) { pEntity->TakeDamage ( pev, pev, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB ); } } } } } //========================================================= // start task //========================================================= void CHoundeye :: StartTask ( Task_t *pTask ) { m_iTaskStatus = TASKSTATUS_RUNNING; switch ( pTask->iTask ) { case TASK_HOUND_FALL_ASLEEP: { m_fAsleep = TRUE; // signal that hound is lying down (must stand again before doing anything else!) m_iTaskStatus = TASKSTATUS_COMPLETE; break; } case TASK_HOUND_WAKE_UP: { m_fAsleep = FALSE; // signal that hound is standing again m_iTaskStatus = TASKSTATUS_COMPLETE; break; } case TASK_HOUND_OPEN_EYE: { m_fDontBlink = FALSE; // turn blinking back on and that code will automatically open the eye m_iTaskStatus = TASKSTATUS_COMPLETE; break; } case TASK_HOUND_CLOSE_EYE: { pev->skin = 0; m_fDontBlink = TRUE; // tell blink code to leave the eye alone. break; } case TASK_HOUND_THREAT_DISPLAY: { m_IdealActivity = ACT_IDLE_ANGRY; break; } case TASK_HOUND_HOP_BACK: { m_IdealActivity = ACT_LEAP; break; } case TASK_RANGE_ATTACK1: { m_IdealActivity = ACT_RANGE_ATTACK1; /* if ( InSquad() ) { // see if there is a battery to connect to. CSquadMonster *pSquad = m_pSquadLeader; while ( pSquad ) { if ( pSquad->m_iMySlot == bits_SLOT_HOUND_BATTERY ) { // draw a beam. MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_BEAMENTS ); WRITE_SHORT( ENTINDEX( this->edict() ) ); WRITE_SHORT( ENTINDEX( pSquad->edict() ) ); WRITE_SHORT( m_iSpriteTexture ); WRITE_BYTE( 0 ); // framestart WRITE_BYTE( 0 ); // framerate WRITE_BYTE( 10 ); // life WRITE_BYTE( 40 ); // width WRITE_BYTE( 10 ); // noise WRITE_BYTE( 0 ); // r, g, b WRITE_BYTE( 50 ); // r, g, b WRITE_BYTE( 250); // r, g, b WRITE_BYTE( 255 ); // brightness WRITE_BYTE( 30 ); // speed MESSAGE_END(); break; } pSquad = pSquad->m_pSquadNext; } } */ break; } case TASK_SPECIAL_ATTACK1: { m_IdealActivity = ACT_SPECIAL_ATTACK1; break; } case TASK_GUARD: { m_IdealActivity = ACT_GUARD; break; } default: { CSquadMonster :: StartTask(pTask); break; } } } //========================================================= // RunTask //========================================================= void CHoundeye :: RunTask ( Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_HOUND_THREAT_DISPLAY: { MakeIdealYaw ( m_vecEnemyLKP ); ChangeYaw ( pev->yaw_speed ); if ( m_fSequenceFinished ) { TaskComplete(); } break; } case TASK_HOUND_CLOSE_EYE: { if ( pev->skin < HOUNDEYE_EYE_FRAMES - 1 ) { pev->skin++; } break; } case TASK_HOUND_HOP_BACK: { if ( m_fSequenceFinished ) { TaskComplete(); } break; } case TASK_SPECIAL_ATTACK1: { pev->skin = RANDOM_LONG(0, HOUNDEYE_EYE_FRAMES - 1); MakeIdealYaw ( m_vecEnemyLKP ); ChangeYaw ( pev->yaw_speed ); float life; life = ((255 - pev->frame) / (pev->framerate * m_flFrameRate)); if (life < 0.1) life = 0.1; MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); WRITE_BYTE( TE_IMPLOSION); WRITE_COORD( pev->origin.x); WRITE_COORD( pev->origin.y); WRITE_COORD( pev->origin.z + 16); WRITE_BYTE( 50 * life + 100); // radius WRITE_BYTE( pev->frame / 25.0 ); // count WRITE_BYTE( life * 10 ); // life MESSAGE_END(); if ( m_fSequenceFinished ) { SonicAttack(); TaskComplete(); } break; } default: { CSquadMonster :: RunTask(pTask); break; } } } //========================================================= // PrescheduleThink //========================================================= void CHoundeye::PrescheduleThink ( void ) { // if the hound is mad and is running, make hunt noises. if ( m_MonsterState == MONSTERSTATE_COMBAT && m_Activity == ACT_RUN && RANDOM_FLOAT( 0, 1 ) < 0.2 ) { WarnSound(); } // at random, initiate a blink if not already blinking or sleeping if ( !m_fDontBlink ) { if ( ( pev->skin == 0 ) && RANDOM_LONG(0,0x7F) == 0 ) { // start blinking! pev->skin = HOUNDEYE_EYE_FRAMES - 1; } else if ( pev->skin != 0 ) { // already blinking pev->skin--; } } // if you are the leader, average the origins of each pack member to get an approximate center. if ( IsLeader() ) { CSquadMonster *pSquadMember; int iSquadCount = 0; for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) { pSquadMember = MySquadMember(i); if (pSquadMember) { iSquadCount++; m_vecPackCenter = m_vecPackCenter + pSquadMember->pev->origin; } } m_vecPackCenter = m_vecPackCenter / iSquadCount; } } //========================================================= // AI Schedules Specific to this monster //========================================================= Task_t tlHoundGuardPack[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_GUARD, (float)0 }, }; Schedule_t slHoundGuardPack[] = { { tlHoundGuardPack, ARRAYSIZE ( tlHoundGuardPack ), bits_COND_SEE_HATE | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_PROVOKED | bits_COND_HEAR_SOUND, bits_SOUND_COMBAT |// sound flags bits_SOUND_WORLD | bits_SOUND_MEAT | bits_SOUND_PLAYER, "GuardPack" }, }; // primary range attack Task_t tlHoundYell1[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_FACE_IDEAL, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_SET_SCHEDULE, (float)SCHED_HOUND_AGITATED }, }; Task_t tlHoundYell2[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_FACE_IDEAL, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, }; Schedule_t slHoundRangeAttack[] = { { tlHoundYell1, ARRAYSIZE ( tlHoundYell1 ), bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE, 0, "HoundRangeAttack1" }, { tlHoundYell2, ARRAYSIZE ( tlHoundYell2 ), bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE, 0, "HoundRangeAttack2" }, }; // lie down and fall asleep Task_t tlHoundSleep[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_WAIT_RANDOM, (float)5 }, { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, { TASK_HOUND_FALL_ASLEEP, (float)0 }, { TASK_WAIT_RANDOM, (float)25 }, { TASK_HOUND_CLOSE_EYE, (float)0 }, //{ TASK_WAIT, (float)10 }, //{ TASK_WAIT_RANDOM, (float)10 }, }; Schedule_t slHoundSleep[] = { { tlHoundSleep, ARRAYSIZE ( tlHoundSleep ), bits_COND_HEAR_SOUND | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_NEW_ENEMY, bits_SOUND_COMBAT | bits_SOUND_PLAYER | bits_SOUND_WORLD, "Hound Sleep" }, }; // wake and stand up lazily Task_t tlHoundWakeLazy[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_HOUND_OPEN_EYE, (float)0 }, { TASK_WAIT_RANDOM, (float)2.5 }, { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, { TASK_HOUND_WAKE_UP, (float)0 }, }; Schedule_t slHoundWakeLazy[] = { { tlHoundWakeLazy, ARRAYSIZE ( tlHoundWakeLazy ), 0, 0, "WakeLazy" }, }; // wake and stand up with great urgency! Task_t tlHoundWakeUrgent[] = { { TASK_HOUND_OPEN_EYE, (float)0 }, { TASK_PLAY_SEQUENCE, (float)ACT_HOP }, { TASK_FACE_IDEAL, (float)0 }, { TASK_HOUND_WAKE_UP, (float)0 }, }; Schedule_t slHoundWakeUrgent[] = { { tlHoundWakeUrgent, ARRAYSIZE ( tlHoundWakeUrgent ), 0, 0, "WakeUrgent" }, }; Task_t tlHoundSpecialAttack1[] = { { TASK_STOP_MOVING, 0 }, { TASK_FACE_IDEAL, (float)0 }, { TASK_SPECIAL_ATTACK1, (float)0 }, { TASK_PLAY_SEQUENCE, (float)ACT_IDLE_ANGRY }, }; Schedule_t slHoundSpecialAttack1[] = { { tlHoundSpecialAttack1, ARRAYSIZE ( tlHoundSpecialAttack1 ), bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_ENEMY_OCCLUDED, 0, "Hound Special Attack1" }, }; Task_t tlHoundAgitated[] = { { TASK_STOP_MOVING, 0 }, { TASK_HOUND_THREAT_DISPLAY, 0 }, }; Schedule_t slHoundAgitated[] = { { tlHoundAgitated, ARRAYSIZE ( tlHoundAgitated ), bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE, 0, "Hound Agitated" }, }; Task_t tlHoundHopRetreat[] = { { TASK_STOP_MOVING, 0 }, { TASK_HOUND_HOP_BACK, 0 }, { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, }; Schedule_t slHoundHopRetreat[] = { { tlHoundHopRetreat, ARRAYSIZE ( tlHoundHopRetreat ), 0, 0, "Hound Hop Retreat" }, }; // hound fails in combat with client in the PVS Task_t tlHoundCombatFailPVS[] = { { TASK_STOP_MOVING, 0 }, { TASK_HOUND_THREAT_DISPLAY, 0 }, { TASK_WAIT_FACE_ENEMY, (float)1 }, }; Schedule_t slHoundCombatFailPVS[] = { { tlHoundCombatFailPVS, ARRAYSIZE ( tlHoundCombatFailPVS ), bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE, 0, "HoundCombatFailPVS" }, }; // hound fails in combat with no client in the PVS. Don't keep peeping! Task_t tlHoundCombatFailNoPVS[] = { { TASK_STOP_MOVING, 0 }, { TASK_HOUND_THREAT_DISPLAY, 0 }, { TASK_WAIT_FACE_ENEMY, (float)2 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_WAIT_PVS, 0 }, }; Schedule_t slHoundCombatFailNoPVS[] = { { tlHoundCombatFailNoPVS, ARRAYSIZE ( tlHoundCombatFailNoPVS ), bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE, 0, "HoundCombatFailNoPVS" }, }; DEFINE_CUSTOM_SCHEDULES( CHoundeye ) { slHoundGuardPack, slHoundRangeAttack, &slHoundRangeAttack[ 1 ], slHoundSleep, slHoundWakeLazy, slHoundWakeUrgent, slHoundSpecialAttack1, slHoundAgitated, slHoundHopRetreat, slHoundCombatFailPVS, slHoundCombatFailNoPVS, }; IMPLEMENT_CUSTOM_SCHEDULES( CHoundeye, CSquadMonster ) //========================================================= // GetScheduleOfType //========================================================= Schedule_t* CHoundeye :: GetScheduleOfType ( int Type ) { if ( m_fAsleep ) { // if the hound is sleeping, must wake and stand! if ( HasConditions( bits_COND_HEAR_SOUND ) ) { CSound *pWakeSound; pWakeSound = PBestSound(); ASSERT( pWakeSound != NULL ); if ( pWakeSound ) { MakeIdealYaw ( pWakeSound->m_vecOrigin ); if ( FLSoundVolume ( pWakeSound ) >= HOUNDEYE_SOUND_STARTLE_VOLUME ) { // awakened by a loud sound return &slHoundWakeUrgent[ 0 ]; } } // sound was not loud enough to scare the bejesus out of houndeye return &slHoundWakeLazy[ 0 ]; } else if ( HasConditions( bits_COND_NEW_ENEMY ) ) { // get up fast, to fight. return &slHoundWakeUrgent[ 0 ]; } else { // hound is waking up on its own return &slHoundWakeLazy[ 0 ]; } } switch ( Type ) { case SCHED_IDLE_STAND: { // we may want to sleep instead of stand! if ( InSquad() && !IsLeader() && !m_fAsleep && RANDOM_LONG(0,29) < 1 ) { return &slHoundSleep[ 0 ]; } else { return CSquadMonster :: GetScheduleOfType( Type ); } } case SCHED_RANGE_ATTACK1: { return &slHoundRangeAttack[ 0 ]; /* if ( InSquad() ) { return &slHoundRangeAttack[ RANDOM_LONG( 0, 1 ) ]; } return &slHoundRangeAttack[ 1 ]; */ } case SCHED_SPECIAL_ATTACK1: { return &slHoundSpecialAttack1[ 0 ]; } case SCHED_GUARD: { return &slHoundGuardPack[ 0 ]; } case SCHED_HOUND_AGITATED: { return &slHoundAgitated[ 0 ]; } case SCHED_HOUND_HOP_RETREAT: { return &slHoundHopRetreat[ 0 ]; } case SCHED_FAIL: { if ( m_MonsterState == MONSTERSTATE_COMBAT ) { if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) { // client in PVS return &slHoundCombatFailPVS[ 0 ]; } else { // client has taken off! return &slHoundCombatFailNoPVS[ 0 ]; } } else { return CSquadMonster :: GetScheduleOfType ( Type ); } } default: { return CSquadMonster :: GetScheduleOfType ( Type ); } } } //========================================================= // GetSchedule //========================================================= Schedule_t *CHoundeye :: GetSchedule( void ) { switch ( m_MonsterState ) { case MONSTERSTATE_COMBAT: { // dead enemy if ( HasConditions( bits_COND_ENEMY_DEAD ) ) { // call base class, all code to handle dead enemies is centralized there. return CBaseMonster :: GetSchedule(); } if ( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) { if ( RANDOM_FLOAT( 0 , 1 ) <= 0.4 ) { TraceResult tr; UTIL_MakeVectors( pev->angles ); UTIL_TraceHull( pev->origin, pev->origin + gpGlobals->v_forward * -128, dont_ignore_monsters, head_hull, ENT( pev ), &tr ); if ( tr.flFraction == 1.0 ) { // it's clear behind, so the hound will jump return GetScheduleOfType ( SCHED_HOUND_HOP_RETREAT ); } } return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); } if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) { if ( OccupySlot ( bits_SLOTS_HOUND_ATTACK ) ) { return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); } return GetScheduleOfType ( SCHED_HOUND_AGITATED ); } break; } default: break; } return CSquadMonster :: GetSchedule(); }