/*** * * 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" #include "weapons.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.0f #define HOUNDEYE_MIN_ATTACK_RADIUS 128 #define HOUNDEYE_SQUAD_BONUS (float)1.1f #define HOUNDEYE_EYE_FRAMES 3 // 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 ) { size_t 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\n" ); 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 ) { float flRadius; if( FBitSet( pev->spawnflags, SF_SQUADMONSTER_NOZAP ) ) flRadius = HOUNDEYE_MIN_ATTACK_RADIUS; else flRadius = HOUNDEYE_MAX_ATTACK_RADIUS; if( flDist <= ( flRadius * 0.5f ) && flDot >= 0.3f ) { 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.0f; pev->velocity.z += ( 0.6f * flGravity ) * 0.5f; 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_RED; 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/rex_bite1.wav" ); PRECACHE_SOUND( "houndeye/rex_bite2.wav" ); PRECACHE_SOUND( "houndeye/rex_bite3.wav" ); 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_attack2.wav" ); PRECACHE_SOUND( "houndeye/he_attack3.wav" ); if( !FBitSet( pev->spawnflags, SF_SQUADMONSTER_NOZAP ) ) { 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 ) { const char *pszSound; if( FBitSet( pev->spawnflags, SF_SQUADMONSTER_NOZAP ) ) { Vector vecMouthPos, vecMouthAng; CBaseEntity *pHurt = 0; pHurt = CheckTraceHullAttack( 80, gSkillData.houndeyeDmgBlast * 0.4f, DMG_SLASH ); if( pHurt ) { if( pHurt->pev->flags & ( FL_CLIENT | FL_MONSTER ) ) { pHurt->pev->punchangle.x = 8; pHurt->pev->punchangle.y = RANDOM_LONG( -8, 8 ); pHurt->pev->punchangle.z = RANDOM_LONG( -8, 8 ); } if( pHurt->IsPlayer() ) { pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100; pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 50; } GetAttachment( 0, vecMouthPos, vecMouthAng ); SpawnBlood( vecMouthPos, pHurt->BloodColor(), 25 ); } switch( RANDOM_LONG( 0, 2 ) ) { case 0: pszSound = "houndeye/rex_bite1.wav"; break; case 1: pszSound = "houndeye/rex_bite2.wav"; break; case 2: pszSound = "houndeye/rex_bite3.wav"; break; } } else { switch( RANDOM_LONG( 0, 2 ) ) { case 0: pszSound = "houndeye/he_attack1.wav"; break; case 1: pszSound = "houndeye/he_attack3.wav"; break; case 2: pszSound = "houndeye/he_attack2.wav"; break; } } EMIT_SOUND( ENT( pev ), CHAN_WEAPON, pszSound, 0.7, ATTN_NORM ); } //========================================================= // 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; if( FBitSet( pev->spawnflags, SF_SQUADMONSTER_NOZAP ) ) { WarnSound(); return; } 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.0f ); WRITE_COORD( pev->origin.x ); WRITE_COORD( pev->origin.y ); WRITE_COORD( pev->origin.z + 16.0f + HOUNDEYE_MAX_ATTACK_RADIUS / 0.2f ); // 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.0f ); WRITE_COORD( pev->origin.x ); WRITE_COORD( pev->origin.y ); WRITE_COORD( pev->origin.z + 16.0f + ( HOUNDEYE_MAX_ATTACK_RADIUS / 2.0f ) / 0.2f ); // 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.5f; } 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++; } if( pev->skin == HOUNDEYE_EYE_FRAMES - 1 ) { TaskComplete(); } 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.1f ) life = 0.1f; 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.0f ); WRITE_BYTE( 50.0f * life + 100.0f ); // radius WRITE_BYTE( pev->frame / 25.0f ); // count WRITE_BYTE( life * 10.0f ); // 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.2f ) { 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, 0.0f }, { TASK_GUARD, 0.0f }, }; 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_INDEFINITE, (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, 1.0f }, }; 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.0f, 1.0f ) <= 0.4f ) { 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.0f ) { // 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(); }