/*** * * 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. * ****/ //========================================================= // Agrunt - Dominant, warlike alien grunt monster //========================================================= #include "extdll.h" #include "util.h" #include "cbase.h" #include "monsters.h" #include "schedule.h" #include "squadmonster.h" #include "weapons.h" #include "soundent.h" #include "hornet.h" //========================================================= // monster-specific schedule types //========================================================= enum { SCHED_AGRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, SCHED_AGRUNT_THREAT_DISPLAY }; //========================================================= // monster-specific tasks //========================================================= enum { TASK_AGRUNT_SETUP_HIDE_ATTACK = LAST_COMMON_TASK + 1, TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE }; int iAgruntMuzzleFlash; //========================================================= // Monster's Anim Events Go Here //========================================================= #define AGRUNT_AE_HORNET1 ( 1 ) #define AGRUNT_AE_HORNET2 ( 2 ) #define AGRUNT_AE_HORNET3 ( 3 ) #define AGRUNT_AE_HORNET4 ( 4 ) #define AGRUNT_AE_HORNET5 ( 5 ) // some events are set up in the QC file that aren't recognized by the code yet. #define AGRUNT_AE_PUNCH ( 6 ) #define AGRUNT_AE_BITE ( 7 ) #define AGRUNT_AE_LEFT_FOOT ( 10 ) #define AGRUNT_AE_RIGHT_FOOT ( 11 ) #define AGRUNT_AE_LEFT_PUNCH ( 12 ) #define AGRUNT_AE_RIGHT_PUNCH ( 13 ) #define AGRUNT_MELEE_DIST 100 class CAGrunt : public CSquadMonster { public: void Spawn( void ); void Precache( void ); void SetYawSpeed( void ); int Classify( void ); int ISoundMask( void ); void HandleAnimEvent( MonsterEvent_t *pEvent ); void SetObjectCollisionBox( void ) { pev->absmin = pev->origin + Vector( -32, -32, 0 ); pev->absmax = pev->origin + Vector( 32, 32, 85 ); } Schedule_t *GetSchedule( void ); Schedule_t *GetScheduleOfType( int Type ); BOOL FCanCheckAttacks( void ); BOOL CheckMeleeAttack1( float flDot, float flDist ); BOOL CheckRangeAttack1( float flDot, float flDist ); void StartTask( Task_t *pTask ); void AlertSound( void ); void DeathSound( void ); void PainSound( void ); void AttackSound( void ); void PrescheduleThink( void ); void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); int IRelationship( CBaseEntity *pTarget ); void StopTalking( void ); BOOL ShouldSpeak( void ); CUSTOM_SCHEDULES virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; static const char *pAttackHitSounds[]; static const char *pAttackMissSounds[]; static const char *pAttackSounds[]; static const char *pDieSounds[]; static const char *pPainSounds[]; static const char *pIdleSounds[]; static const char *pAlertSounds[]; BOOL m_fCanHornetAttack; float m_flNextHornetAttackCheck; float m_flNextPainTime; // three hacky fields for speech stuff. These don't really need to be saved. float m_flNextSpeakTime; float m_flNextWordTime; int m_iLastWord; }; LINK_ENTITY_TO_CLASS( monster_alien_grunt, CAGrunt ) TYPEDESCRIPTION CAGrunt::m_SaveData[] = { DEFINE_FIELD( CAGrunt, m_fCanHornetAttack, FIELD_BOOLEAN ), DEFINE_FIELD( CAGrunt, m_flNextHornetAttackCheck, FIELD_TIME ), DEFINE_FIELD( CAGrunt, m_flNextPainTime, FIELD_TIME ), DEFINE_FIELD( CAGrunt, m_flNextSpeakTime, FIELD_TIME ), DEFINE_FIELD( CAGrunt, m_flNextWordTime, FIELD_TIME ), DEFINE_FIELD( CAGrunt, m_iLastWord, FIELD_INTEGER ), }; IMPLEMENT_SAVERESTORE( CAGrunt, CSquadMonster ) const char *CAGrunt::pAttackHitSounds[] = { "zombie/claw_strike1.wav", "zombie/claw_strike2.wav", "zombie/claw_strike3.wav", }; const char *CAGrunt::pAttackMissSounds[] = { "zombie/claw_miss1.wav", "zombie/claw_miss2.wav", }; const char *CAGrunt::pAttackSounds[] = { "agrunt/ag_attack1.wav", "agrunt/ag_attack2.wav", "agrunt/ag_attack3.wav", }; const char *CAGrunt::pDieSounds[] = { "agrunt/ag_die1.wav", "agrunt/ag_die4.wav", "agrunt/ag_die5.wav", }; const char *CAGrunt::pPainSounds[] = { "agrunt/ag_pain1.wav", "agrunt/ag_pain2.wav", "agrunt/ag_pain3.wav", "agrunt/ag_pain4.wav", "agrunt/ag_pain5.wav", }; const char *CAGrunt::pIdleSounds[] = { "agrunt/ag_idle1.wav", "agrunt/ag_idle2.wav", "agrunt/ag_idle3.wav", "agrunt/ag_idle4.wav", }; const char *CAGrunt::pAlertSounds[] = { "agrunt/ag_alert1.wav", "agrunt/ag_alert3.wav", "agrunt/ag_alert4.wav", "agrunt/ag_alert5.wav", }; //========================================================= // IRelationship - overridden because Human Grunts are // Alien Grunt's nemesis. //========================================================= int CAGrunt::IRelationship( CBaseEntity *pTarget ) { if( FClassnameIs( pTarget->pev, "monster_human_grunt" ) ) { return R_NM; } return CSquadMonster::IRelationship( pTarget ); } //========================================================= // ISoundMask //========================================================= int CAGrunt::ISoundMask( void ) { return ( bits_SOUND_WORLD | bits_SOUND_COMBAT | bits_SOUND_PLAYER | bits_SOUND_DANGER ); } //========================================================= // TraceAttack //========================================================= void CAGrunt::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) { if( ptr->iHitgroup == 10 && ( bitsDamageType & ( DMG_BULLET | DMG_SLASH | DMG_CLUB ) ) ) { // hit armor if( pev->dmgtime != gpGlobals->time || ( RANDOM_LONG( 0, 10 ) < 1 ) ) { UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2 ) ); pev->dmgtime = gpGlobals->time; } if( RANDOM_LONG( 0, 1 ) == 0 ) { Vector vecTracerDir = vecDir; vecTracerDir.x += RANDOM_FLOAT( -0.3, 0.3 ); vecTracerDir.y += RANDOM_FLOAT( -0.3, 0.3 ); vecTracerDir.z += RANDOM_FLOAT( -0.3, 0.3 ); vecTracerDir = vecTracerDir * -512; MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, ptr->vecEndPos ); WRITE_BYTE( TE_TRACER ); WRITE_COORD( ptr->vecEndPos.x ); WRITE_COORD( ptr->vecEndPos.y ); WRITE_COORD( ptr->vecEndPos.z ); WRITE_COORD( vecTracerDir.x ); WRITE_COORD( vecTracerDir.y ); WRITE_COORD( vecTracerDir.z ); MESSAGE_END(); } flDamage -= 20; if( flDamage <= 0 ) flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated } else { SpawnBlood( ptr->vecEndPos, BloodColor(), flDamage );// a little surface blood. TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); } AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); } //========================================================= // StopTalking - won't speak again for 10-20 seconds. //========================================================= void CAGrunt::StopTalking( void ) { m_flNextWordTime = m_flNextSpeakTime = gpGlobals->time + 10 + RANDOM_LONG( 0, 10 ); } //========================================================= // ShouldSpeak - Should this agrunt be talking? //========================================================= BOOL CAGrunt::ShouldSpeak( void ) { if( m_flNextSpeakTime > gpGlobals->time ) { // my time to talk is still in the future. return FALSE; } if( pev->spawnflags & SF_MONSTER_GAG ) { if( m_MonsterState != MONSTERSTATE_COMBAT ) { // if gagged, don't talk outside of combat. // if not going to talk because of this, put the talk time // into the future a bit, so we don't talk immediately after // going into combat m_flNextSpeakTime = gpGlobals->time + 3; return FALSE; } } return TRUE; } //========================================================= // PrescheduleThink //========================================================= void CAGrunt::PrescheduleThink( void ) { if( ShouldSpeak() ) { if( m_flNextWordTime < gpGlobals->time ) { int num = -1; do { num = RANDOM_LONG( 0, ARRAYSIZE( pIdleSounds ) - 1 ); } while( num == m_iLastWord ); m_iLastWord = num; // play a new sound EMIT_SOUND( ENT( pev ), CHAN_VOICE, pIdleSounds[num], 1.0, ATTN_NORM ); // is this word our last? if( RANDOM_LONG( 1, 10 ) <= 1 ) { // stop talking. StopTalking(); } else { m_flNextWordTime = gpGlobals->time + RANDOM_FLOAT( 0.5, 1 ); } } } } //========================================================= // DieSound //========================================================= void CAGrunt::DeathSound( void ) { StopTalking(); EMIT_SOUND( ENT( pev ), CHAN_VOICE, pDieSounds[RANDOM_LONG( 0, ARRAYSIZE( pDieSounds ) - 1 )], 1.0, ATTN_NORM ); } //========================================================= // AlertSound //========================================================= void CAGrunt::AlertSound( void ) { StopTalking(); EMIT_SOUND( ENT( pev ), CHAN_VOICE, pAlertSounds[RANDOM_LONG( 0, ARRAYSIZE( pAlertSounds ) - 1 )], 1.0, ATTN_NORM ); } //========================================================= // AttackSound //========================================================= void CAGrunt::AttackSound( void ) { StopTalking(); EMIT_SOUND( ENT( pev ), CHAN_VOICE, pAttackSounds[RANDOM_LONG( 0, ARRAYSIZE( pAttackSounds ) - 1 )], 1.0, ATTN_NORM ); } //========================================================= // PainSound //========================================================= void CAGrunt::PainSound( void ) { if( m_flNextPainTime > gpGlobals->time ) { return; } m_flNextPainTime = gpGlobals->time + 0.6; StopTalking(); EMIT_SOUND( ENT( pev ), CHAN_VOICE, pPainSounds[RANDOM_LONG( 0, ARRAYSIZE( pPainSounds ) - 1 )], 1.0, ATTN_NORM ); } //========================================================= // Classify - indicates this monster's place in the // relationship table. //========================================================= int CAGrunt::Classify( void ) { return CLASS_ALIEN_MILITARY; } //========================================================= // SetYawSpeed - allows each sequence to have a different // turn rate associated with it. //========================================================= void CAGrunt::SetYawSpeed( void ) { int ys; switch( m_Activity ) { case ACT_TURN_LEFT: case ACT_TURN_RIGHT: ys = 110; break; default: ys = 100; break; } pev->yaw_speed = ys; } //========================================================= // HandleAnimEvent - catches the monster-specific messages // that occur when tagged animation frames are played. // // Returns number of events handled, 0 if none. //========================================================= void CAGrunt::HandleAnimEvent( MonsterEvent_t *pEvent ) { switch( pEvent->event ) { case AGRUNT_AE_HORNET1: case AGRUNT_AE_HORNET2: case AGRUNT_AE_HORNET3: case AGRUNT_AE_HORNET4: case AGRUNT_AE_HORNET5: { // m_vecEnemyLKP should be center of enemy body Vector vecArmPos, vecArmDir; Vector vecDirToEnemy; Vector angDir; if( HasConditions( bits_COND_SEE_ENEMY ) ) { vecDirToEnemy = ( ( m_vecEnemyLKP ) - pev->origin ); angDir = UTIL_VecToAngles( vecDirToEnemy ); vecDirToEnemy = vecDirToEnemy.Normalize(); } else { angDir = pev->angles; UTIL_MakeAimVectors( angDir ); vecDirToEnemy = gpGlobals->v_forward; } pev->effects = EF_MUZZLEFLASH; // make angles +-180 if( angDir.x > 180 ) { angDir.x = angDir.x - 360; } SetBlending( 0, angDir.x ); GetAttachment( 0, vecArmPos, vecArmDir ); vecArmPos = vecArmPos + vecDirToEnemy * 32; MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecArmPos ); WRITE_BYTE( TE_SPRITE ); WRITE_COORD( vecArmPos.x ); // pos WRITE_COORD( vecArmPos.y ); WRITE_COORD( vecArmPos.z ); WRITE_SHORT( iAgruntMuzzleFlash ); // model WRITE_BYTE( 6 ); // size * 10 WRITE_BYTE( 128 ); // brightness MESSAGE_END(); CBaseEntity *pHornet = CBaseEntity::Create( "hornet", vecArmPos, UTIL_VecToAngles( vecDirToEnemy ), edict() ); UTIL_MakeVectors ( pHornet->pev->angles ); pHornet->pev->velocity = gpGlobals->v_forward * 300; switch( RANDOM_LONG ( 0 , 2 ) ) { case 0: EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, "agrunt/ag_fire1.wav", 1.0, ATTN_NORM, 0, 100 ); break; case 1: EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, "agrunt/ag_fire2.wav", 1.0, ATTN_NORM, 0, 100 ); break; case 2: EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, "agrunt/ag_fire3.wav", 1.0, ATTN_NORM, 0, 100 ); break; } CBaseMonster *pHornetMonster = pHornet->MyMonsterPointer(); if( pHornetMonster ) { pHornetMonster->m_hEnemy = m_hEnemy; } } break; case AGRUNT_AE_LEFT_FOOT: switch( RANDOM_LONG( 0, 1 ) ) { // left foot case 0: EMIT_SOUND_DYN( ENT( pev ), CHAN_BODY, "player/pl_ladder2.wav", 1, ATTN_NORM, 0, 70 ); break; case 1: EMIT_SOUND_DYN( ENT( pev ), CHAN_BODY, "player/pl_ladder4.wav", 1, ATTN_NORM, 0, 70 ); break; } break; case AGRUNT_AE_RIGHT_FOOT: // right foot switch( RANDOM_LONG( 0, 1 ) ) { case 0: EMIT_SOUND_DYN( ENT( pev ), CHAN_BODY, "player/pl_ladder1.wav", 1, ATTN_NORM, 0, 70 ); break; case 1: EMIT_SOUND_DYN( ENT( pev ), CHAN_BODY, "player/pl_ladder3.wav", 1, ATTN_NORM, 0 ,70); break; } break; case AGRUNT_AE_LEFT_PUNCH: { CBaseEntity *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, gSkillData.agruntDmgPunch, DMG_CLUB ); if( pHurt ) { pHurt->pev->punchangle.y = -25; pHurt->pev->punchangle.x = 8; // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above. if( pHurt->IsPlayer() ) { // this is a player. Knock him around. pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * 250; } EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, pAttackHitSounds[RANDOM_LONG( 0, ARRAYSIZE( pAttackHitSounds ) - 1 )], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG( -5, 5 ) ); Vector vecArmPos, vecArmAng; GetAttachment( 0, vecArmPos, vecArmAng ); SpawnBlood( vecArmPos, pHurt->BloodColor(), 25 );// a little surface blood. } else { // Play a random attack miss sound EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, pAttackMissSounds[RANDOM_LONG( 0, ARRAYSIZE( pAttackMissSounds ) - 1 )], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG( -5, 5 ) ); } } break; case AGRUNT_AE_RIGHT_PUNCH: { CBaseEntity *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, gSkillData.agruntDmgPunch, DMG_CLUB ); if( pHurt ) { pHurt->pev->punchangle.y = 25; pHurt->pev->punchangle.x = 8; // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above. if( pHurt->IsPlayer() ) { // this is a player. Knock him around. pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * -250; } EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, pAttackHitSounds[RANDOM_LONG( 0, ARRAYSIZE( pAttackHitSounds ) - 1 )], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG( -5, 5 ) ); Vector vecArmPos, vecArmAng; GetAttachment( 0, vecArmPos, vecArmAng ); SpawnBlood( vecArmPos, pHurt->BloodColor(), 25 );// a little surface blood. } else { // Play a random attack miss sound EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, pAttackMissSounds[RANDOM_LONG( 0, ARRAYSIZE( pAttackMissSounds ) - 1 )], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG( -5, 5 ) ); } } break; default: CSquadMonster::HandleAnimEvent( pEvent ); break; } } //========================================================= // Spawn //========================================================= void CAGrunt::Spawn() { Precache(); SET_MODEL( ENT( pev ), "models/agrunt.mdl" ); UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_STEP; m_bloodColor = BLOOD_COLOR_GREEN; pev->effects = 0; pev->health = gSkillData.agruntHealth; m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) m_MonsterState = MONSTERSTATE_NONE; m_afCapability = 0; m_afCapability |= bits_CAP_SQUAD; m_HackedGunPos = Vector( 24, 64, 48 ); m_flNextSpeakTime = m_flNextWordTime = gpGlobals->time + 10 + RANDOM_LONG( 0, 10 ); MonsterInit(); } //========================================================= // Precache - precaches all resources this monster needs //========================================================= void CAGrunt::Precache() { size_t i; PRECACHE_MODEL( "models/agrunt.mdl" ); for( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) PRECACHE_SOUND( (char *)pAttackHitSounds[i] ); for( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) PRECACHE_SOUND( (char *)pAttackMissSounds[i] ); for( i = 0; i < ARRAYSIZE( pIdleSounds ); i++ ) PRECACHE_SOUND( (char *)pIdleSounds[i] ); for( i = 0; i < ARRAYSIZE( pDieSounds ); i++ ) PRECACHE_SOUND( (char *)pDieSounds[i] ); for( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) PRECACHE_SOUND( (char *)pPainSounds[i] ); for( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) PRECACHE_SOUND( (char *)pAttackSounds[i] ); for( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) PRECACHE_SOUND( (char *)pAlertSounds[i] ); PRECACHE_SOUND( "hassault/hw_shoot1.wav" ); iAgruntMuzzleFlash = PRECACHE_MODEL( "sprites/muz4.spr" ); UTIL_PrecacheOther( "hornet" ); } //========================================================= // AI Schedules Specific to this monster //========================================================= //========================================================= // Fail Schedule //========================================================= Task_t tlAGruntFail[] = { { TASK_STOP_MOVING, 0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_WAIT, (float)2 }, { TASK_WAIT_PVS, (float)0 }, }; Schedule_t slAGruntFail[] = { { tlAGruntFail, ARRAYSIZE( tlAGruntFail ), bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1, 0, "AGrunt Fail" }, }; //========================================================= // Combat Fail Schedule //========================================================= Task_t tlAGruntCombatFail[] = { { TASK_STOP_MOVING, 0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_WAIT_FACE_ENEMY, (float)2 }, { TASK_WAIT_PVS, (float)0 }, }; Schedule_t slAGruntCombatFail[] = { { tlAGruntCombatFail, ARRAYSIZE( tlAGruntCombatFail ), bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1, 0, "AGrunt Combat Fail" }, }; //========================================================= // Standoff schedule. Used in combat when a monster is // hiding in cover or the enemy has moved out of sight. // Should we look around in this schedule? //========================================================= Task_t tlAGruntStandoff[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_WAIT_FACE_ENEMY, (float)2 }, }; Schedule_t slAGruntStandoff[] = { { tlAGruntStandoff, ARRAYSIZE( tlAGruntStandoff ), bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1 | bits_COND_SEE_ENEMY | bits_COND_NEW_ENEMY | bits_COND_HEAR_SOUND, bits_SOUND_DANGER, "Agrunt Standoff" } }; //========================================================= // Suppress //========================================================= Task_t tlAGruntSuppressHornet[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, }; Schedule_t slAGruntSuppress[] = { { tlAGruntSuppressHornet, ARRAYSIZE( tlAGruntSuppressHornet ), 0, 0, "AGrunt Suppress Hornet", }, }; //========================================================= // primary range attacks //========================================================= Task_t tlAGruntRangeAttack1[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, }; Schedule_t slAGruntRangeAttack1[] = { { tlAGruntRangeAttack1, ARRAYSIZE( tlAGruntRangeAttack1 ), bits_COND_NEW_ENEMY | bits_COND_ENEMY_DEAD | bits_COND_HEAVY_DAMAGE, 0, "AGrunt Range Attack1" }, }; Task_t tlAGruntHiddenRangeAttack1[] = { { TASK_SET_FAIL_SCHEDULE, (float)SCHED_STANDOFF }, { TASK_AGRUNT_SETUP_HIDE_ATTACK, 0 }, { TASK_STOP_MOVING, 0 }, { TASK_FACE_IDEAL, 0 }, { TASK_RANGE_ATTACK1_NOTURN, (float)0 }, }; Schedule_t slAGruntHiddenRangeAttack[] = { { tlAGruntHiddenRangeAttack1, ARRAYSIZE ( tlAGruntHiddenRangeAttack1 ), bits_COND_NEW_ENEMY | bits_COND_HEAVY_DAMAGE | bits_COND_HEAR_SOUND, bits_SOUND_DANGER, "AGrunt Hidden Range Attack1" }, }; //========================================================= // Take cover from enemy! Tries lateral cover before node // cover! //========================================================= Task_t tlAGruntTakeCoverFromEnemy[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_WAIT, (float)0.2 }, { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, { TASK_RUN_PATH, (float)0 }, { TASK_WAIT_FOR_MOVEMENT, (float)0 }, { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, { TASK_FACE_ENEMY, (float)0 }, }; Schedule_t slAGruntTakeCoverFromEnemy[] = { { tlAGruntTakeCoverFromEnemy, ARRAYSIZE( tlAGruntTakeCoverFromEnemy ), bits_COND_NEW_ENEMY, 0, "AGruntTakeCoverFromEnemy" }, }; //========================================================= // Victory dance! //========================================================= Task_t tlAGruntVictoryDance[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_SET_FAIL_SCHEDULE, (float)SCHED_AGRUNT_THREAT_DISPLAY }, { TASK_WAIT, (float)0.2 }, { TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, { TASK_WALK_PATH, (float)0 }, { TASK_WAIT_FOR_MOVEMENT, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, { TASK_PLAY_SEQUENCE, (float)ACT_THREAT_DISPLAY }, { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, }; Schedule_t slAGruntVictoryDance[] = { { tlAGruntVictoryDance, ARRAYSIZE( tlAGruntVictoryDance ), bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE, 0, "AGruntVictoryDance" }, }; //========================================================= //========================================================= Task_t tlAGruntThreatDisplay[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_PLAY_SEQUENCE, (float)ACT_THREAT_DISPLAY }, }; Schedule_t slAGruntThreatDisplay[] = { { tlAGruntThreatDisplay, ARRAYSIZE( tlAGruntThreatDisplay ), bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE, bits_SOUND_PLAYER | bits_SOUND_COMBAT | bits_SOUND_WORLD, "AGruntThreatDisplay" }, }; DEFINE_CUSTOM_SCHEDULES( CAGrunt ) { slAGruntFail, slAGruntCombatFail, slAGruntStandoff, slAGruntSuppress, slAGruntRangeAttack1, slAGruntHiddenRangeAttack, slAGruntTakeCoverFromEnemy, slAGruntVictoryDance, slAGruntThreatDisplay, }; IMPLEMENT_CUSTOM_SCHEDULES( CAGrunt, CSquadMonster ) //========================================================= // FCanCheckAttacks - this is overridden for alien grunts // because they can use their smart weapons against unseen // enemies. Base class doesn't attack anyone it can't see. //========================================================= BOOL CAGrunt::FCanCheckAttacks( void ) { if( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) { return TRUE; } else { return FALSE; } } //========================================================= // CheckMeleeAttack1 - alien grunts zap the crap out of // any enemy that gets too close. //========================================================= BOOL CAGrunt::CheckMeleeAttack1( float flDot, float flDist ) { if( HasConditions( bits_COND_SEE_ENEMY ) && flDist <= AGRUNT_MELEE_DIST && flDot >= 0.6 && m_hEnemy != 0 ) { return TRUE; } return FALSE; } //========================================================= // CheckRangeAttack1 // // !!!LATER - we may want to load balance this. Several // tracelines are done, so we may not want to do this every // server frame. Definitely not while firing. //========================================================= BOOL CAGrunt::CheckRangeAttack1( float flDot, float flDist ) { if( gpGlobals->time < m_flNextHornetAttackCheck ) { return m_fCanHornetAttack; } if( HasConditions( bits_COND_SEE_ENEMY ) && flDist >= AGRUNT_MELEE_DIST && flDist <= 1024 && flDot >= 0.5 && NoFriendlyFire() ) { TraceResult tr; Vector vecArmPos, vecArmDir; // verify that a shot fired from the gun will hit the enemy before the world. // !!!LATER - we may wish to do something different for projectile weapons as opposed to instant-hit UTIL_MakeVectors( pev->angles ); GetAttachment( 0, vecArmPos, vecArmDir ); //UTIL_TraceLine( vecArmPos, vecArmPos + gpGlobals->v_forward * 256, ignore_monsters, ENT( pev ), &tr ); UTIL_TraceLine( vecArmPos, m_hEnemy->BodyTarget( vecArmPos ), dont_ignore_monsters, ENT( pev ), &tr ); if( tr.flFraction == 1.0 || tr.pHit == m_hEnemy->edict() ) { m_flNextHornetAttackCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 ); m_fCanHornetAttack = TRUE; return m_fCanHornetAttack; } } m_flNextHornetAttackCheck = gpGlobals->time + 0.2;// don't check for half second if this check wasn't successful m_fCanHornetAttack = FALSE; return m_fCanHornetAttack; } //========================================================= // StartTask //========================================================= void CAGrunt::StartTask( Task_t *pTask ) { switch( pTask->iTask ) { case TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE: { UTIL_MakeVectors( pev->angles ); if( BuildRoute( m_vecEnemyLKP - gpGlobals->v_forward * 50, bits_MF_TO_LOCATION, NULL ) ) { TaskComplete(); } else { ALERT( at_aiconsole, "AGruntGetPathToEnemyCorpse failed!!\n" ); TaskFail(); } } break; case TASK_AGRUNT_SETUP_HIDE_ATTACK: // alien grunt shoots hornets back out into the open from a concealed location. // try to find a spot to throw that gives the smart weapon a good chance of finding the enemy. // ideally, this spot is along a line that is perpendicular to a line drawn from the agrunt to the enemy. CBaseMonster *pEnemyMonsterPtr; pEnemyMonsterPtr = m_hEnemy->MyMonsterPointer(); if( pEnemyMonsterPtr ) { Vector vecCenter; TraceResult tr; BOOL fSkip; fSkip = FALSE; vecCenter = Center(); UTIL_VecToAngles( m_vecEnemyLKP - pev->origin ); UTIL_TraceLine( Center() + gpGlobals->v_forward * 128, m_vecEnemyLKP, ignore_monsters, ENT( pev ), &tr ); if( tr.flFraction == 1.0 ) { MakeIdealYaw( pev->origin + gpGlobals->v_right * 128 ); fSkip = TRUE; TaskComplete(); } if( !fSkip ) { UTIL_TraceLine( Center() - gpGlobals->v_forward * 128, m_vecEnemyLKP, ignore_monsters, ENT( pev ), &tr ); if( tr.flFraction == 1.0 ) { MakeIdealYaw( pev->origin - gpGlobals->v_right * 128 ); fSkip = TRUE; TaskComplete(); } } if( !fSkip ) { UTIL_TraceLine( Center() + gpGlobals->v_forward * 256, m_vecEnemyLKP, ignore_monsters, ENT( pev ), &tr ); if( tr.flFraction == 1.0 ) { MakeIdealYaw( pev->origin + gpGlobals->v_right * 256 ); fSkip = TRUE; TaskComplete(); } } if( !fSkip ) { UTIL_TraceLine( Center() - gpGlobals->v_forward * 256, m_vecEnemyLKP, ignore_monsters, ENT( pev ), &tr ); if( tr.flFraction == 1.0 ) { MakeIdealYaw( pev->origin - gpGlobals->v_right * 256 ); fSkip = TRUE; TaskComplete(); } } if( !fSkip ) { TaskFail(); } } else { ALERT( at_aiconsole, "AGRunt - no enemy monster ptr!!!\n" ); TaskFail(); } break; default: CSquadMonster::StartTask( pTask ); break; } } //========================================================= // GetSchedule - Decides which type of schedule best suits // the monster's current state and conditions. Then calls // monster's member function to get a pointer to a schedule // of the proper type. //========================================================= Schedule_t *CAGrunt::GetSchedule( void ) { if( HasConditions( bits_COND_HEAR_SOUND ) ) { CSound *pSound; pSound = PBestSound(); ASSERT( pSound != NULL ); if( pSound && ( pSound->m_iType & bits_SOUND_DANGER ) ) { // dangerous sound nearby! return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); } } 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_NEW_ENEMY ) ) { return GetScheduleOfType( SCHED_WAKE_ANGRY ); } // zap player! if( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) { AttackSound();// this is a total hack. Should be parto f the schedule return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); } if( HasConditions( bits_COND_HEAVY_DAMAGE ) ) { return GetScheduleOfType( SCHED_SMALL_FLINCH ); } // can attack if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot ( bits_SLOTS_AGRUNT_HORNET ) ) { return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); } if( OccupySlot ( bits_SLOT_AGRUNT_CHASE ) ) { return GetScheduleOfType( SCHED_CHASE_ENEMY ); } return GetScheduleOfType( SCHED_STANDOFF ); } break; default: break; } return CSquadMonster::GetSchedule(); } //========================================================= //========================================================= Schedule_t *CAGrunt::GetScheduleOfType( int Type ) { switch( Type ) { case SCHED_TAKE_COVER_FROM_ENEMY: return &slAGruntTakeCoverFromEnemy[0]; break; case SCHED_RANGE_ATTACK1: if( HasConditions( bits_COND_SEE_ENEMY ) ) { //normal attack return &slAGruntRangeAttack1[0]; } else { // attack an unseen enemy // return &slAGruntHiddenRangeAttack[0]; return &slAGruntRangeAttack1[0]; } break; case SCHED_AGRUNT_THREAT_DISPLAY: return &slAGruntThreatDisplay[0]; break; case SCHED_AGRUNT_SUPPRESS: return &slAGruntSuppress[0]; break; case SCHED_STANDOFF: return &slAGruntStandoff[0]; break; case SCHED_VICTORY_DANCE: return &slAGruntVictoryDance[0]; break; case SCHED_FAIL: // no fail schedule specified, so pick a good generic one. { if( m_hEnemy != 0 ) { // I have an enemy // !!!LATER - what if this enemy is really far away and i'm chasing him? // this schedule will make me stop, face his last known position for 2 // seconds, and then try to move again return &slAGruntCombatFail[0]; } return &slAGruntFail[0]; } break; } return CSquadMonster::GetScheduleOfType( Type ); }