/*** * * Copyright (c) 1996-2001, 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. * ****/ //========================================================= // terror //========================================================= //========================================================= // Hit groups! //========================================================= /* 1 - Head 2 - Stomach 3 - Gun */ #include "extdll.h" #include "plane.h" #include "util.h" #include "cbase.h" #include "monsters.h" #include "schedule.h" #include "animation.h" #include "squadmonster.h" #include "weapons.h" #include "talkmonster.h" #include "soundent.h" #include "effects.h" #include "customentity.h" #include "hgrunt.h" int g_fTerrorQuestion; // true if an idle grunt asked a question. Cleared when someone answers. extern DLL_GLOBAL int g_iSkillLevel; //========================================================= // monster-specific DEFINE's //========================================================= #define TERROR_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! #define TERROR_VOL 0.35 // volume of grunt sounds #define TERROR_ATTN ATTN_NORM // attenutation of grunt sentences #define TERROR_LIMP_HEALTH 20 #define TERROR_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot. #define TERROR_NUM_HEADS 2 // how many grunt heads are there? #define TERROR_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill #define TERROR_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences #define TERROR_MP5 ( 1 << 0) #define TERROR_HANDGRENADE ( 1 << 1) #define TERROR_GRENADELAUNCHER ( 1 << 2) #define TERROR_SHOTGUN ( 1 << 3) #define HEAD_GROUP 1 #define HEAD_TERROR 0 #define HEAD_COMMANDER 1 #define HEAD_SHOTGUN 2 #define HEAD_M203 3 #define GUN_GROUP 2 #define GUN_MP5 0 #define GUN_SHOTGUN 1 #define GUN_NONE 2 //========================================================= // Monster's Anim Events Go Here //========================================================= #define TERROR_AE_RELOAD ( 2 ) #define TERROR_AE_KICK ( 3 ) #define TERROR_AE_BURST1 ( 4 ) #define TERROR_AE_BURST2 ( 5 ) #define TERROR_AE_BURST3 ( 6 ) #define TERROR_AE_GREN_TOSS ( 7 ) #define TERROR_AE_GREN_LAUNCH ( 8 ) #define TERROR_AE_GREN_DROP ( 9 ) #define TERROR_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad. #define TERROR_AE_DROP_GUN ( 11) // grunt (probably dead) is dropping his mp5. //========================================================= // monster-specific schedule types //========================================================= enum { SCHED_TERROR_SUPPRESS = LAST_COMMON_SCHEDULE + 1, SCHED_TERROR_ESTABLISH_LINE_OF_FIRE,// move to a location to set up an attack against the enemy. (usually when a friendly is in the way). SCHED_TERROR_COVER_AND_RELOAD, SCHED_TERROR_SWEEP, SCHED_TERROR_FOUND_ENEMY, SCHED_TERROR_REPEL, SCHED_TERROR_REPEL_ATTACK, SCHED_TERROR_REPEL_LAND, SCHED_TERROR_WAIT_FACE_ENEMY, SCHED_TERROR_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. SCHED_TERROR_ELOF_FAIL, }; //========================================================= // monster-specific tasks //========================================================= enum { TASK_TERROR_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, TASK_TERROR_SPEAK_SENTENCE, TASK_TERROR_CHECK_FIRE, }; //========================================================= // monster-specific conditions //========================================================= #define bits_COND_TERROR_NOFIRE ( bits_COND_SPECIAL1 ) class CTerror : public CHGrunt { public: void Spawn( void ); void Precache( void ); //int Classify ( void ); void HandleAnimEvent( MonsterEvent_t *pEvent ); void IdleSound ( void ); void GibMonster( void ); void SpeakSentence( void ); Schedule_t *GetSchedule( void ); Schedule_t *GetScheduleOfType ( int Type ); int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); int IRelationship ( CBaseEntity *pTarget ); static const char *pTerrorSentences[]; }; LINK_ENTITY_TO_CLASS( monster_human_terror, CTerror ); const char *CTerror::pTerrorSentences[] = { "TR_GREN", // grenade scared grunt "TR_ALERT", // sees player "TR_MONSTER", // sees monster "TR_COVER", // running to cover "TR_THROW", // about to throw grenade "TR_CHARGE", // running out to get the enemy "TR_TAUNT", // say rude things }; typedef enum { TERROR_SENT_NONE = -1, TERROR_SENT_GREN = 0, TERROR_SENT_ALERT, TERROR_SENT_MONSTER, TERROR_SENT_COVER, TERROR_SENT_THROW, TERROR_SENT_CHARGE, TERROR_SENT_TAUNT, } TERROR_SENTENCE_TYPES; //========================================================= // Speak Sentence - say your cued up sentence. // // Some grunt sentences (take cover and charge) rely on actually // being able to execute the intended action. It's really lame // when a grunt says 'COVER ME' and then doesn't move. The problem // is that the sentences were played when the decision to TRY // to move to cover was made. Now the sentence is played after // we know for sure that there is a valid path. The schedule // may still fail but in most cases, well after the grunt has // started moving. //========================================================= void CTerror :: SpeakSentence( void ) { if ( m_iSentence == TERROR_SENT_NONE ) { // no sentence cued up. return; } if (FOkToSpeak()) { SENTENCEG_PlayRndSz( ENT(pev), pTerrorSentences[ m_iSentence ], TERROR_SENTENCE_VOLUME, TERROR_ATTN, 0, m_voicePitch); JustSpoke(); } } //========================================================= // IRelationship - overridden because Alien Terrors are // Human Terror's nemesis. //========================================================= int CTerror::IRelationship ( CBaseEntity *pTarget ) { if ( FClassnameIs( pTarget->pev, "monster_alien_grunt" ) || ( FClassnameIs( pTarget->pev, "monster_gargantua" ) ) ) { return R_NM; } return CSquadMonster::IRelationship( pTarget ); } //========================================================= // GibMonster - make gun fly through the air. //========================================================= void CTerror :: GibMonster ( void ) { /* Vector vecGunPos; Vector vecGunAngles; if ( GetBodygroup( 2 ) != 2 ) {// throw a gun if the grunt has one GetAttachment( 0, vecGunPos, vecGunAngles ); CBaseEntity *pGun; if (FBitSet( pev->weapons, TERROR_SHOTGUN )) { pGun = DropItem( "weapon_barneyshotgun", vecGunPos, vecGunAngles ); } else { pGun = DropItem( "weapon_barney9mmar", vecGunPos, vecGunAngles ); } if ( pGun ) { pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); } if (FBitSet( pev->weapons, TERROR_GRENADELAUNCHER )) { pGun = DropItem( "ammo_ARgrenades", vecGunPos, vecGunAngles ); if ( pGun ) { pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); } } } */ CBaseMonster :: GibMonster(); } //========================================================= // TakeDamage - overridden for the grunt because the grunt // needs to forget that he is in cover if he's hurt. (Obviously // not in a safe place anymore). //========================================================= int CTerror :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { Forget( bits_MEMORY_INCOVER ); return CSquadMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); } void CTerror :: IdleSound( void ) { if (FOkToSpeak() && (g_fTerrorQuestion || RANDOM_LONG(0,1))) { if (!g_fTerrorQuestion) { // ask question or make statement switch (RANDOM_LONG(0,2)) { case 0: // check in SENTENCEG_PlayRndSz(ENT(pev), "TR_CHECK", TERROR_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); g_fTerrorQuestion = 1; break; case 1: // question SENTENCEG_PlayRndSz(ENT(pev), "TR_QUEST", TERROR_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); g_fTerrorQuestion = 2; break; case 2: // statement SENTENCEG_PlayRndSz(ENT(pev), "TR_IDLE", TERROR_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); break; } } else { switch (g_fTerrorQuestion) { case 1: // check in SENTENCEG_PlayRndSz(ENT(pev), "TR_CLEAR", TERROR_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); break; case 2: // question SENTENCEG_PlayRndSz(ENT(pev), "TR_ANSWER", TERROR_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); break; } g_fTerrorQuestion = 0; } JustSpoke(); } } //========================================================= // HandleAnimEvent - catches the monster-specific messages // that occur when tagged animation frames are played. //========================================================= void CTerror :: HandleAnimEvent( MonsterEvent_t *pEvent ) { Vector vecShootDir; Vector vecShootOrigin; switch( pEvent->event ) { case TERROR_AE_DROP_GUN: break; case TERROR_AE_RELOAD: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_reload1.wav", 1, ATTN_NORM ); m_cAmmoLoaded = m_cClipSize; ClearConditions(bits_COND_NO_AMMO_LOADED); break; case TERROR_AE_GREN_TOSS: { UTIL_MakeVectors( pev->angles ); // CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector (0, 0, 32), m_vecTossVelocity, 3.5 ); CGrenade::ShootTimed( pev, GetGunPosition(), m_vecTossVelocity, 3.5 ); m_fThrowGrenade = FALSE; m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. // !!!LATER - when in a group, only try to throw grenade if ordered. } break; case TERROR_AE_GREN_LAUNCH: { EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM); CGrenade::ShootContact( pev, GetGunPosition(), m_vecTossVelocity, gSkillData.plrDmgM203Grenade ); m_fThrowGrenade = FALSE; if (g_iSkillLevel == SKILL_HARD) m_flNextGrenadeCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 );// wait a random amount of time before shooting again else m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. } break; case TERROR_AE_GREN_DROP: { UTIL_MakeVectors( pev->angles ); CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 17 - gpGlobals->v_right * 27 + gpGlobals->v_up * 6, g_vecZero, 3 ); } break; case TERROR_AE_BURST1: { if ( FBitSet( pev->weapons, TERROR_MP5 )) { Shoot(); // the first round of the three round burst plays the sound and puts a sound in the world sound list. if ( RANDOM_LONG(0,1) ) { EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_mgun1.wav", 1, ATTN_NORM ); } else { EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_mgun2.wav", 1, ATTN_NORM ); } } else { Shotgun( ); EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM ); } CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); } break; case TERROR_AE_BURST2: case TERROR_AE_BURST3: Shoot(); break; case TERROR_AE_KICK: { CBaseEntity *pHurt = Kick(); if ( pHurt ) { // SOUND HERE! UTIL_MakeVectors( pev->angles ); pHurt->pev->punchangle.x = 15; pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; pHurt->TakeDamage( pev, pev, gSkillData.hgruntDmgKick, DMG_CLUB ); } } break; case TERROR_AE_CAUGHT_ENEMY: { if ( FOkToSpeak() ) { SENTENCEG_PlayRndSz(ENT(pev), "TR_ALERT", TERROR_SENTENCE_VOLUME, TERROR_ATTN, 0, m_voicePitch); JustSpoke(); } } break; default: CSquadMonster::HandleAnimEvent( pEvent ); break; } } //========================================================= // Spawn //========================================================= void CTerror :: Spawn() { Precache( ); SET_MODEL(ENT(pev), "models/terror.mdl"); UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_STEP; m_bloodColor = BLOOD_COLOR_RED; pev->effects = 0; pev->health = gSkillData.hgruntHealth; m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) m_MonsterState = MONSTERSTATE_NONE; m_flNextGrenadeCheck = gpGlobals->time + 1; m_flNextPainTime = gpGlobals->time; m_iSentence = TERROR_SENT_NONE; m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; m_fEnemyEluded = FALSE; m_fFirstEncounter = TRUE;// this is true when the grunt spawns, because he hasn't encountered an enemy yet. m_HackedGunPos = Vector ( 0, 0, 55 ); if (pev->weapons == 0) { // initialize to original values pev->weapons = TERROR_MP5 | TERROR_HANDGRENADE; // pev->weapons = TERROR_SHOTGUN; // pev->weapons = TERROR_9MMM41A | TERROR_GRENADELAUNCHER; } if (FBitSet( pev->weapons, TERROR_SHOTGUN )) { SetBodygroup( GUN_GROUP, GUN_SHOTGUN ); m_cClipSize = 8; } else { m_cClipSize = 48; } m_cAmmoLoaded = m_cClipSize; if (FBitSet( pev->weapons, TERROR_SHOTGUN )) { SetBodygroup( HEAD_GROUP, HEAD_SHOTGUN); } else if (FBitSet( pev->weapons, TERROR_GRENADELAUNCHER )) { SetBodygroup( HEAD_GROUP, HEAD_M203 ); } CTalkMonster::g_talkWaitTime = 0; MonsterInit(); } //========================================================= // Precache - precaches all resources this monster needs //========================================================= void CTerror :: Precache() { PRECACHE_MODEL("models/terror.mdl"); PRECACHE_SOUND( "hgrunt/gr_mgun1.wav" ); PRECACHE_SOUND( "hgrunt/gr_mgun2.wav" ); PRECACHE_SOUND( "hgrunt/gr_die1.wav" ); PRECACHE_SOUND( "hgrunt/gr_die2.wav" ); PRECACHE_SOUND( "hgrunt/gr_die3.wav" ); PRECACHE_SOUND( "hgrunt/gr_pain1.wav" ); PRECACHE_SOUND( "hgrunt/gr_pain2.wav" ); PRECACHE_SOUND( "hgrunt/gr_pain3.wav" ); PRECACHE_SOUND( "hgrunt/gr_pain4.wav" ); PRECACHE_SOUND( "hgrunt/gr_pain5.wav" ); PRECACHE_SOUND( "hgrunt/gr_reload1.wav" ); PRECACHE_SOUND( "weapons/glauncher.wav" ); PRECACHE_SOUND( "weapons/sbarrel1.wav" ); PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event // get voice pitch m_voicePitch = 120 + RANDOM_LONG(0,2); m_iBrassShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell m_iShotgunShell = PRECACHE_MODEL ("models/shotgunshell.mdl"); } //========================================================= // AI Schedules Specific to this monster //========================================================= extern Schedule_t slIdleStand[]; extern Schedule_t slGruntFail[]; extern Schedule_t slGruntCombatFail[]; extern Schedule_t slGruntVictoryDance[]; extern Schedule_t slGruntEstablishLineOfFire[]; extern Schedule_t slGruntFoundEnemy[]; extern Schedule_t slGruntCombatFace[]; extern Schedule_t slGruntSignalSuppress[]; extern Schedule_t slGruntSuppress[]; extern Schedule_t slGruntWaitInCover[]; extern Schedule_t slGruntTakeCover[]; extern Schedule_t slGruntGrenadeCover[]; extern Schedule_t slGruntTossGrenadeCover[]; extern Schedule_t slGruntTakeCoverFromBestSound[]; extern Schedule_t slGruntHideReload[]; extern Schedule_t slGruntSweep[]; extern Schedule_t slGruntRangeAttack1A[]; extern Schedule_t slGruntRangeAttack1B[]; extern Schedule_t slGruntRangeAttack2[]; extern Schedule_t slGruntRepel[]; extern Schedule_t slGruntRepelAttack[]; extern Schedule_t slGruntRepelLand[]; //========================================================= // Get Schedule! //========================================================= Schedule_t *CTerror :: GetSchedule( void ) { // clear old sentence m_iSentence = TERROR_SENT_NONE; // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling. if ( pev->movetype == MOVETYPE_FLY && m_MonsterState != MONSTERSTATE_PRONE ) { if (pev->flags & FL_ONGROUND) { // just landed pev->movetype = MOVETYPE_STEP; return GetScheduleOfType ( SCHED_TERROR_REPEL_LAND ); } else { // repel down a rope, if ( m_MonsterState == MONSTERSTATE_COMBAT ) return GetScheduleOfType ( SCHED_TERROR_REPEL_ATTACK ); else return GetScheduleOfType ( SCHED_TERROR_REPEL ); } } // grunts place HIGH priority on running away from danger sounds. if ( HasConditions(bits_COND_HEAR_SOUND) ) { CSound *pSound; pSound = PBestSound(); ASSERT( pSound != NULL ); if ( pSound) { if (pSound->m_iType & bits_SOUND_DANGER) { // dangerous sound nearby! //!!!KELLY - currently, this is the grunt's signal that a grenade has landed nearby, // and the grunt should find cover from the blast // good place for "SHIT!" or some other colorful verbal indicator of dismay. // It's not safe to play a verbal order here "Scatter", etc cause // this may only affect a single individual in a squad. if (FOkToSpeak()) { SENTENCEG_PlayRndSz( ENT(pev), "TR_GREN", TERROR_SENTENCE_VOLUME, TERROR_ATTN, 0, m_voicePitch); JustSpoke(); } return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); } /* if (!HasConditions( bits_COND_SEE_ENEMY ) && ( pSound->m_iType & (bits_SOUND_PLAYER | bits_SOUND_COMBAT) )) { MakeIdealYaw( pSound->m_vecOrigin ); } */ } } 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(); } // new enemy if ( HasConditions(bits_COND_NEW_ENEMY) ) { if ( InSquad() ) { MySquadLeader()->m_fEnemyEluded = FALSE; if ( !IsLeader() ) { return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); } else { //!!!KELLY - the leader of a squad of grunts has just seen the player or a // monster and has made it the squad's enemy. You // can check pev->flags for FL_CLIENT to determine whether this is the player // or a monster. He's going to immediately start // firing, though. If you'd like, we can make an alternate "first sight" // schedule where the leader plays a handsign anim // that gives us enough time to hear a short sentence or spoken command // before he starts pluggin away. if (FOkToSpeak())// && RANDOM_LONG(0,1)) { if ((m_hEnemy != 0) && m_hEnemy->IsPlayer()) // player SENTENCEG_PlayRndSz( ENT(pev), "TR_ALERT", TERROR_SENTENCE_VOLUME, TERROR_ATTN, 0, m_voicePitch); else if ((m_hEnemy != 0) && (m_hEnemy->Classify() != CLASS_PLAYER_ALLY) && (m_hEnemy->Classify() != CLASS_HUMAN_PASSIVE) && (m_hEnemy->Classify() != CLASS_MACHINE)) // monster SENTENCEG_PlayRndSz( ENT(pev), "TR_MONST", TERROR_SENTENCE_VOLUME, TERROR_ATTN, 0, m_voicePitch); JustSpoke(); } if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) { return GetScheduleOfType ( SCHED_TERROR_SUPPRESS ); } else { return GetScheduleOfType ( SCHED_TERROR_ESTABLISH_LINE_OF_FIRE ); } } } } // no ammo else if ( HasConditions ( bits_COND_NO_AMMO_LOADED ) ) { //!!!KELLY - this individual just realized he's out of bullet ammo. // He's going to try to find cover to run to and reload, but rarely, if // none is available, he'll drop and reload in the open here. return GetScheduleOfType ( SCHED_TERROR_COVER_AND_RELOAD ); } // damaged just a little else if ( HasConditions( bits_COND_LIGHT_DAMAGE ) ) { // if hurt: // 90% chance of taking cover // 10% chance of flinch. int iPercent = RANDOM_LONG(0,99); if ( iPercent <= 90 && m_hEnemy != 0 ) { // only try to take cover if we actually have an enemy! //!!!KELLY - this grunt was hit and is going to run to cover. if (FOkToSpeak()) // && RANDOM_LONG(0,1)) { //SENTENCEG_PlayRndSz( ENT(pev), "TR_COVER", TERROR_SENTENCE_VOLUME, TERROR_ATTN, 0, m_voicePitch); m_iSentence = TERROR_SENT_COVER; //JustSpoke(); } return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); } else { return GetScheduleOfType( SCHED_SMALL_FLINCH ); } } // can kick else if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) { return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); } // can grenade launch else if ( FBitSet( pev->weapons, TERROR_GRENADELAUNCHER) && HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) { // shoot a grenade if you can return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); } // can shoot else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) { if ( InSquad() ) { // if the enemy has eluded the squad and a squad member has just located the enemy // and the enemy does not see the squad member, issue a call to the squad to waste a // little time and give the player a chance to turn. if ( MySquadLeader()->m_fEnemyEluded && !HasConditions ( bits_COND_ENEMY_FACING_ME ) ) { MySquadLeader()->m_fEnemyEluded = FALSE; return GetScheduleOfType ( SCHED_TERROR_FOUND_ENEMY ); } } if ( OccupySlot ( bits_SLOTS_HGRUNT_ENGAGE ) ) { // try to take an available ENGAGE slot return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); } else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) { // throw a grenade if can and no engage slots are available return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); } else { // hide! return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); } } // can't see enemy else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) { if ( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) { //!!!KELLY - this grunt is about to throw or fire a grenade at the player. Great place for "fire in the hole" "frag out" etc if (FOkToSpeak()) { SENTENCEG_PlayRndSz( ENT(pev), "TR_THROW", TERROR_SENTENCE_VOLUME, TERROR_ATTN, 0, m_voicePitch); JustSpoke(); } return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); } else if ( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) { //!!!KELLY - grunt cannot see the enemy and has just decided to // charge the enemy's position. if (FOkToSpeak())// && RANDOM_LONG(0,1)) { //SENTENCEG_PlayRndSz( ENT(pev), "TR_CHARGE", TERROR_SENTENCE_VOLUME, TERROR_ATTN, 0, m_voicePitch); m_iSentence = TERROR_SENT_CHARGE; //JustSpoke(); } return GetScheduleOfType( SCHED_TERROR_ESTABLISH_LINE_OF_FIRE ); } else { //!!!KELLY - grunt is going to stay put for a couple seconds to see if // the enemy wanders back out into the open, or approaches the // grunt's covered position. Good place for a taunt, I guess? if (FOkToSpeak() && RANDOM_LONG(0,1)) { SENTENCEG_PlayRndSz( ENT(pev), "TR_TAUNT", TERROR_SENTENCE_VOLUME, TERROR_ATTN, 0, m_voicePitch); JustSpoke(); } return GetScheduleOfType( SCHED_STANDOFF ); } } if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) { return GetScheduleOfType ( SCHED_TERROR_ESTABLISH_LINE_OF_FIRE ); } } default: break; } // no special cases here, call the base class return CSquadMonster :: GetSchedule(); } //========================================================= //========================================================= Schedule_t* CTerror :: GetScheduleOfType ( int Type ) { switch ( Type ) { case SCHED_TAKE_COVER_FROM_ENEMY: { if ( InSquad() ) { if ( g_iSkillLevel == SKILL_HARD && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) { if (FOkToSpeak()) { SENTENCEG_PlayRndSz( ENT(pev), "TR_THROW", TERROR_SENTENCE_VOLUME, TERROR_ATTN, 0, m_voicePitch); JustSpoke(); } return slGruntTossGrenadeCover; } else { return &slGruntTakeCover[ 0 ]; } } else { if ( RANDOM_LONG(0,1) ) { return &slGruntTakeCover[ 0 ]; } else { return &slGruntGrenadeCover[ 0 ]; } } } case SCHED_TAKE_COVER_FROM_BEST_SOUND: { return &slGruntTakeCoverFromBestSound[ 0 ]; } case SCHED_TERROR_TAKECOVER_FAILED: { if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) { return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); } return GetScheduleOfType ( SCHED_FAIL ); } break; case SCHED_TERROR_ELOF_FAIL: { // human grunt is unable to move to a position that allows him to attack the enemy. return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); } break; case SCHED_TERROR_ESTABLISH_LINE_OF_FIRE: { return &slGruntEstablishLineOfFire[ 0 ]; } break; case SCHED_RANGE_ATTACK1: { // randomly stand or crouch if (RANDOM_LONG(0,9) == 0) m_fStanding = RANDOM_LONG(0,1); if (m_fStanding) return &slGruntRangeAttack1B[ 0 ]; else return &slGruntRangeAttack1A[ 0 ]; } case SCHED_RANGE_ATTACK2: { return &slGruntRangeAttack2[ 0 ]; } case SCHED_COMBAT_FACE: { return &slGruntCombatFace[ 0 ]; } case SCHED_TERROR_WAIT_FACE_ENEMY: { return &slGruntWaitInCover[ 0 ]; } case SCHED_TERROR_SWEEP: { return &slGruntSweep[ 0 ]; } case SCHED_TERROR_COVER_AND_RELOAD: { return &slGruntHideReload[ 0 ]; } case SCHED_TERROR_FOUND_ENEMY: { return &slGruntFoundEnemy[ 0 ]; } case SCHED_VICTORY_DANCE: { if ( InSquad() ) { if ( !IsLeader() ) { return &slGruntFail[ 0 ]; } } return &slGruntVictoryDance[ 0 ]; } case SCHED_TERROR_SUPPRESS: { if ( m_hEnemy->IsPlayer() && m_fFirstEncounter ) { m_fFirstEncounter = FALSE;// after first encounter, leader won't issue handsigns anymore when he has a new enemy return &slGruntSignalSuppress[ 0 ]; } else { return &slGruntSuppress[ 0 ]; } } case SCHED_FAIL: { if ( m_hEnemy != 0 ) { // grunt has an enemy, so pick a different default fail schedule most likely to help recover. return &slGruntCombatFail[ 0 ]; } return &slGruntFail[ 0 ]; } case SCHED_TERROR_REPEL: { if (pev->velocity.z > -128) pev->velocity.z -= 32; return &slGruntRepel[ 0 ]; } case SCHED_TERROR_REPEL_ATTACK: { if (pev->velocity.z > -128) pev->velocity.z -= 32; return &slGruntRepelAttack[ 0 ]; } case SCHED_TERROR_REPEL_LAND: { return &slGruntRepelLand[ 0 ]; } default: { return CSquadMonster :: GetScheduleOfType ( Type ); } } } //========================================================= // CTerrorRepel - when triggered, spawns a monster_human_grunt // repelling down a line. //========================================================= class CTerrorRepel : public CBaseMonster { public: void Spawn( void ); void Precache( void ); void EXPORT RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); int m_iSpriteTexture; // Don't save, precache }; LINK_ENTITY_TO_CLASS( monster_terror_repel, CTerrorRepel ); void CTerrorRepel::Spawn( void ) { Precache( ); pev->solid = SOLID_NOT; SetUse( &CTerrorRepel::RepelUse ); } void CTerrorRepel::Precache( void ) { UTIL_PrecacheOther( "monster_human_terror" ); m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); } void CTerrorRepel::RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { TraceResult tr; UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0), dont_ignore_monsters, ENT(pev), &tr); CBaseEntity *pEntity = Create( "monster_human_terror", pev->origin, pev->angles ); CBaseMonster *pTerror = pEntity->MyMonsterPointer( ); pTerror->pev->movetype = MOVETYPE_FLY; pTerror->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); pTerror->SetActivity( ACT_GLIDE ); // UNDONE: position? pTerror->m_vecLastPosition = tr.vecEndPos; CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); pBeam->PointEntInit( pev->origin + Vector(0,0,112), pTerror->entindex() ); pBeam->SetFlags( BEAM_FSOLID ); pBeam->SetColor( 255, 255, 255 ); pBeam->SetThink( &CBeam::SUB_Remove ); pBeam->pev->nextthink = gpGlobals->time + -4096.0f * tr.flFraction / pTerror->pev->velocity.z + 0.5f; UTIL_Remove( this ); } //========================================================= // DEAD TERROR PROP //========================================================= class CDeadTerror : public CBaseMonster { public: void Spawn( void ); int Classify ( void ) { return CLASS_HUMAN_MILITARY; } void KeyValue( KeyValueData *pkvd ); int m_iPose;// which sequence to display -- temporary, don't need to save static const char *m_szPoses[3]; }; const char *CDeadTerror::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; void CDeadTerror::KeyValue( KeyValueData *pkvd ) { if (FStrEq(pkvd->szKeyName, "pose")) { m_iPose = atoi(pkvd->szValue); pkvd->fHandled = TRUE; } else CBaseMonster::KeyValue( pkvd ); } LINK_ENTITY_TO_CLASS( monster_terror_dead, CDeadTerror ); //========================================================= // ********** DeadTerror SPAWN ********** //========================================================= void CDeadTerror :: Spawn( void ) { PRECACHE_MODEL("models/terror.mdl"); SET_MODEL(ENT(pev), "models/terror.mdl"); pev->effects = 0; pev->yaw_speed = 8; pev->sequence = 0; m_bloodColor = BLOOD_COLOR_RED; pev->sequence = LookupSequence( m_szPoses[m_iPose] ); if (pev->sequence == -1) { ALERT ( at_console, "Dead terror with bad pose\n" ); } // Corpses have less health pev->health = 8; // map old bodies onto new bodies switch( pev->body ) { case 0: // Terror with Gun pev->body = 0; pev->skin = 0; SetBodygroup( HEAD_GROUP, HEAD_TERROR ); SetBodygroup( GUN_GROUP, GUN_MP5 ); break; case 1: // Commander with Gun pev->body = 0; pev->skin = 0; SetBodygroup( HEAD_GROUP, HEAD_COMMANDER ); SetBodygroup( GUN_GROUP, GUN_MP5 ); break; case 2: // Terror no Gun pev->body = 0; pev->skin = 0; SetBodygroup( HEAD_GROUP, HEAD_TERROR ); SetBodygroup( GUN_GROUP, GUN_NONE ); break; case 3: // Commander no Gun pev->body = 0; pev->skin = 0; SetBodygroup( HEAD_GROUP, HEAD_COMMANDER ); SetBodygroup( GUN_GROUP, GUN_NONE ); break; } MonsterInitDead(); }