//========================================================= // Opposing Forces Monster Gonome // // Made by Demiurge // //FGD monster_human_torch //========================================================= //========================================================= // Human Torch ally //========================================================= #include "extdll.h" #include "plane.h" #include "util.h" #include "cbase.h" #include "monsters.h" #include "schedule.h" #include "animation.h" #include "weapons.h" #include "soundent.h" #include "effects.h" #include "customentity.h" #include "decals.h" #include "defaultai.h" #include "scripted.h" #include "squadmonster.h" #include "talkmonster.h" #include "basemonster.h" int g_fTorchQuestion; // true if an idle grunt asked a question. Cleared when someone answers. extern DLL_GLOBAL int g_iSkillLevel; //========================================================= // monster-specific DEFINE's //========================================================= #define TORCH_CLIP_SIZE 17 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! #define TORCH_VOL 0.35 // volume of grunt sounds #define TORCH_ATTN ATTN_NORM // attenutation of grunt sentences #define HTORCH_LIMP_HEALTH 20 #define HTORCH_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a TORCH with a single headshot. #define HTORCH_NUM_HEADS 1 // how many grunt heads are there? #define HTORCH_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill #define HTORCH_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences #define HTORCH_EAGLE ( 1 << 0) #define HTORCH_NOGUN ( 1 << 3) #define HEAD_TORCH 0 #define GUN_EAGLE 0 #define GUN_TORCH 1 #define GUN_NONE 2 //========================================================= // Monster's Anim Events Go Here //========================================================= #define HTORCH_AE_RELOAD ( 2 ) #define HTORCH_AE_KICK ( 3 ) #define HTORCH_AE_BURST1 ( 4 ) #define HTORCH_AE_BURST2 ( 5 ) #define HTORCH_AE_BURST3 ( 6 ) #define HTORCH_AE_GREN_TOSS ( 7 ) #define HTORCH_AE_GREN_LAUNCH ( 8 ) #define HTORCH_AE_GREN_DROP ( 9 ) #define HTORCH_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad. #define HTORCH_AE_DROP_GUN ( 11) // grunt (probably dead) is dropping his mp5. #define HTORCH_AE_SHOWGUN ( 17) #define HTORCH_AE_SHOWTORCH ( 18) #define HTORCH_AE_HIDETORCH ( 19) #define HTORCH_AE_ONGAS ( 20) #define HTORCH_AE_OFFGAS ( 21) //========================================================= // monster-specific schedule types //========================================================= enum { SCHED_TORCH_SUPPRESS = LAST_COMMON_SCHEDULE + 1, SCHED_TORCH_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_TORCH_COVER_AND_RELOAD, SCHED_TORCH_SWEEP, SCHED_TORCH_FOUND_ENEMY, SCHED_TORCH_REPEL, SCHED_TORCH_REPEL_ATTACK, SCHED_TORCH_REPEL_LAND, SCHED_TORCH_WAIT_FACE_ENEMY, SCHED_TORCH_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. SCHED_TORCH_ELOF_FAIL, }; //========================================================= // monster-specific tasks //========================================================= enum { TASK_TORCH_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, TASK_TORCH_SPEAK_SENTENCE, TASK_TORCH_CHECK_FIRE, }; //========================================================= // monster-specific conditions //========================================================= #define bits_COND_TORCH_NOFIRE ( bits_COND_SPECIAL1 ) class CHTorch : public CSquadMonster { public: void Spawn( void ); void Precache( void ); void SetYawSpeed ( void ); int Classify ( void ); int ISoundMask ( void ); void HandleAnimEvent( MonsterEvent_t *pEvent ); BOOL FCanCheckAttacks ( void ); BOOL CheckMeleeAttack1 ( float flDot, float flDist ); BOOL CheckRangeAttack1 ( float flDot, float flDist ); void CheckAmmo ( void ); void SetActivity ( Activity NewActivity ); void StartTask ( Task_t *pTask ); void RunTask ( Task_t *pTask ); virtual int ObjectCaps( void ) { return CSquadMonster :: ObjectCaps() | FCAP_IMPULSE_USE; } void DeathSound( void ); void PainSound( void ); void IdleSound ( void ); Vector GetGunPosition( void ); void Shoot ( void ); void MakeGas( void ); void UpdateGas( void ); void KillGas( void ); inline BOOL BeamIsOn( void ) { return m_pBeam != NULL; } void DeclineFollowing( void ); void PrescheduleThink ( void ); void GibMonster( void ); void SpeakSentence( void ); int Save( CSave &save ); int Restore( CRestore &restore ); CBaseEntity *Kick( void ); Schedule_t *GetScheduleOfType ( int Type ); Schedule_t *GetSchedule ( void ); MONSTERSTATE GetIdealState ( void ); // void TalkInit( void ); void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); void FollowingUse( void ); void IsFollowing( void ); int IRelationship ( CBaseEntity *pTarget ); BOOL FOkToSpeak( void ); void JustSpoke( void ); CUSTOM_SCHEDULES; static TYPEDESCRIPTION m_SaveData[]; // checking the feasibility of a grenade toss is kind of costly, so we do it every couple of seconds, // not every server frame. Vector m_vecDir; Vector m_vecEnd; CBeam *m_pBeam; float m_gasTime; float m_flNextGrenadeCheck; float m_flNextPainTime; float m_flLastEnemySightTime; Vector m_vecTossVelocity; BOOL m_fThrowGrenade; BOOL m_fStanding; BOOL m_fFirstEncounter;// only put on the handsign show in the squad's first encounter. int m_cClipSize; int m_voicePitch; int m_iBrassShell; int m_iSentence; float m_flPlayerDamage;// how much pain has the player inflicted on me? static const char *pTorchSentences[]; }; LINK_ENTITY_TO_CLASS( monster_human_torch, CHTorch ); TYPEDESCRIPTION CHTorch::m_SaveData[] = { DEFINE_FIELD( CHTorch, m_pBeam, FIELD_CLASSPTR ), DEFINE_FIELD( CHTorch, m_gasTime, FIELD_TIME ), DEFINE_FIELD( CHTorch, m_flNextGrenadeCheck, FIELD_TIME ), DEFINE_FIELD( CHTorch, m_flNextPainTime, FIELD_TIME ), // DEFINE_FIELD( CHTorch, m_flLastEnemySightTime, FIELD_TIME ), // don't save, go to zero DEFINE_FIELD( CHTorch, m_vecTossVelocity, FIELD_VECTOR ), DEFINE_FIELD( CHTorch, m_fThrowGrenade, FIELD_BOOLEAN ), DEFINE_FIELD( CHTorch, m_fStanding, FIELD_BOOLEAN ), DEFINE_FIELD( CHTorch, m_fFirstEncounter, FIELD_BOOLEAN ), DEFINE_FIELD( CHTorch, m_cClipSize, FIELD_INTEGER ), DEFINE_FIELD( CHTorch, m_voicePitch, FIELD_INTEGER ), DEFINE_FIELD( CHTorch, m_iSentence, FIELD_INTEGER ), }; IMPLEMENT_SAVERESTORE( CHTorch, CSquadMonster ); const char *CHTorch::pTorchSentences[] = { "HG_GREN", // grenade scared grunt // "HG_ALERT", // sees player "HG_MONSTER", // sees monster "HG_COVER", // running to cover "HG_THROW", // about to throw grenade "HG_CHARGE", // running out to get the enemy "HG_TAUNT", // say rude things }; enum { HTORCH_SENT_NONE = -1, HTORCH_SENT_GREN = 0, HTORCH_SENT_ALERT, HTORCH_SENT_MONSTER, HTORCH_SENT_COVER, HTORCH_SENT_THROW, HTORCH_SENT_CHARGE, HTORCH_SENT_TAUNT, } HTORCH_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 CHTorch :: SpeakSentence( void ) { if ( m_iSentence == HTORCH_SENT_NONE ) { // no sentence cued up. return; } if (FOkToSpeak()) { SENTENCEG_PlayRndSz( ENT(pev), pTorchSentences[ m_iSentence ], HTORCH_SENTENCE_VOLUME, TORCH_ATTN, 0, m_voicePitch); JustSpoke(); } } //========================================================= // IRelationship - overridden because Alien Grunts are // Human Grunt's nemesis. //========================================================= int CHTorch::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 CHTorch :: GibMonster ( void ) { Vector vecGunPos; Vector vecGunAngles; // if ( GetBodygroup( 2 ) != 2 ) pev->skin = 0; {// throw a gun if the grunt has one GetAttachment( 0, vecGunPos, vecGunAngles ); CBaseEntity *pGun = NULL; if (FBitSet( pev->weapons, HTORCH_EAGLE )) { pGun = DropItem( "weapon_eagle", 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 ); } } SetUse( NULL ); CBaseMonster :: GibMonster(); } //========================================================= // ISoundMask - Overidden for human grunts because they // hear the DANGER sound that is made by hand grenades and // other dangerous items. //========================================================= int CHTorch :: ISoundMask ( void ) { return bits_SOUND_WORLD | bits_SOUND_COMBAT | bits_SOUND_PLAYER | bits_SOUND_DANGER; } //========================================================= // someone else is talking - don't speak //========================================================= BOOL CHTorch :: FOkToSpeak( void ) { // if someone else is talking, don't speak if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) return FALSE; if ( pev->spawnflags & SF_MONSTER_GAG ) { if ( m_MonsterState != MONSTERSTATE_COMBAT ) { // no talking outside of combat if gagged. return FALSE; } } // if player is not in pvs, don't speak // if (FNullEnt(FIND_CLIENT_IN_PVS(edict()))) // return FALSE; return TRUE; } //========================================================= //========================================================= void CHTorch :: JustSpoke( void ) { CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(1.5, 2.0); m_iSentence = HTORCH_SENT_NONE; } //========================================================= // PrescheduleThink - this function runs after conditions // are collected and before scheduling code is run. //========================================================= void CHTorch :: PrescheduleThink ( void ) { if (m_pBeam) { UpdateGas(); } if ( InSquad() && m_hEnemy != 0 ) { if ( HasConditions ( bits_COND_SEE_ENEMY ) ) { // update the squad's last enemy sighting time. MySquadLeader()->m_flLastEnemySightTime = gpGlobals->time; } else { if ( gpGlobals->time - MySquadLeader()->m_flLastEnemySightTime > 5 ) { // been a while since we've seen the enemy MySquadLeader()->m_fEnemyEluded = TRUE; } } } } //========================================================= // FCanCheckAttacks - this is overridden for human grunts // because they can throw/shoot grenades when they can't see their // target and the base class doesn't check attacks if the monster // cannot see its enemy. // // !!!BUGBUG - this gets called before a 3-round burst is fired // which means that a friendly can still be hit with up to 2 rounds. // ALSO, grenades will not be tossed if there is a friendly in front, // this is a bad bug. Friendly machine gun fire avoidance // will unecessarily prevent the throwing of a grenade as well. //========================================================= BOOL CHTorch :: FCanCheckAttacks ( void ) { if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) { return TRUE; } else { return FALSE; } } //========================================================= // CheckMeleeAttack1 //========================================================= BOOL CHTorch :: CheckMeleeAttack1 ( float flDot, float flDist ) { CBaseMonster *pEnemy; if ( m_hEnemy != 0 ) { pEnemy = m_hEnemy->MyMonsterPointer(); if ( !pEnemy ) { return FALSE; } } if ( flDist <= 64 && flDot >= 0.7f && pEnemy->Classify() != CLASS_ALIEN_BIOWEAPON && pEnemy->Classify() != CLASS_PLAYER_BIOWEAPON ) { return TRUE; } return FALSE; } //========================================================= // CheckRangeAttack1 - overridden for HTorch, cause // FCanCheckAttacks() doesn't disqualify all attacks based // on whether or not the enemy is occluded because unlike // the base class, the HTorch can attack when the enemy is // occluded (throw grenade over wall, etc). We must // disqualify the machine gun attack if the enemy is occluded. //========================================================= BOOL CHTorch :: CheckRangeAttack1 ( float flDot, float flDist ) { if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 2048 && flDot >= 0.5f && NoFriendlyFire() ) { TraceResult tr; if ( !m_hEnemy->IsPlayer() && flDist <= 64 ) { // kick nonclients, but don't shoot at them. return FALSE; } Vector vecSrc = GetGunPosition(); // verify that a bullet fired from the gun will hit the enemy before the world. UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); if ( tr.flFraction == 1.0f ) { return TRUE; } } return FALSE; } //========================================================= // TraceAttack - make sure we're not taking it in the helmet //========================================================= void CHTorch :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { // check for helmet shot if (ptr->iHitgroup == 11) { // make sure we're wearing one if (GetBodygroup( 1 ) == HEAD_TORCH && (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB))) { // absorb damage flDamage -= 20; if (flDamage <= 0) { UTIL_Ricochet( ptr->vecEndPos, 1.0 ); flDamage = 0.01; } } // it's head shot anyways ptr->iHitgroup = HITGROUP_HEAD; } CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); } static BOOL IsFacing( entvars_t *pevTest, const Vector &reference ) { Vector vecDir = (reference - pevTest->origin); vecDir.z = 0; vecDir = vecDir.Normalize(); Vector forward, angle; angle = pevTest->v_angle; angle.x = 0; UTIL_MakeVectorsPrivate( angle, forward, NULL, NULL ); // He's facing me, he meant it if ( DotProduct( forward, vecDir ) > 0.96f ) // +/- 15 degrees or so { return TRUE; } return FALSE; } //========================================================= // TakeDamage - overridden for the torch because the torch // needs to forget that he is in cover if he's hurt. (Obviously // not in a safe place anymore). //========================================================= int CHTorch :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { // make sure friends team about it if player hurts talkmonsters... int ret = CSquadMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); if ( !IsAlive() || pev->deadflag == DEAD_DYING ) return ret; if ( m_MonsterState != MONSTERSTATE_PRONE && (pevAttacker->flags & FL_CLIENT) ) { m_flPlayerDamage += flDamage; // This is a heurstic to determine if the player intended to harm me // If I have an enemy, we can't establish intent (may just be crossfire) if ( m_hEnemy == 0 ) { // If the player was facing directly at me, or I'm already suspicious, get mad if ( (m_afMemory & bits_MEMORY_SUSPICIOUS) || IsFacing( pevAttacker, pev->origin ) ) { // Alright, now I'm pissed! // PlaySentence( "BA_MAD", 4, VOL_NORM, ATTN_NORM ); Remember( bits_MEMORY_PROVOKED ); StopFollowing( TRUE ); } else { // Hey, be careful with that // PlaySentence( "BA_SHOT", 4, VOL_NORM, ATTN_NORM ); Remember( bits_MEMORY_SUSPICIOUS ); } } /* else if ( !(m_hEnemy->IsPlayer()) && pev->deadflag == DEAD_NO ) { PlaySentence( "BA_SHOT", 4, VOL_NORM, ATTN_NORM ); }*/ } // return ret; Forget( bits_MEMORY_INCOVER ); return CSquadMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); } //========================================================= // SetYawSpeed - allows each sequence to have a different // turn rate associated with it. //========================================================= void CHTorch :: SetYawSpeed ( void ) { int ys; switch ( m_Activity ) { case ACT_IDLE: ys = 150; break; case ACT_RUN: ys = 150; break; case ACT_WALK: ys = 180; break; case ACT_RANGE_ATTACK1: ys = 120; break; case ACT_MELEE_ATTACK1: ys = 120; break; case ACT_TURN_LEFT: case ACT_TURN_RIGHT: ys = 180; break; case ACT_GLIDE: case ACT_FLY: ys = 30; break; default: ys = 90; break; } pev->yaw_speed = ys; } void CHTorch :: IdleSound( void ) { if (FOkToSpeak() && (g_fTorchQuestion || RANDOM_LONG(0,1))) { if (!g_fTorchQuestion) { // ask question or make statement switch (RANDOM_LONG(0,2)) { case 0: // check in SENTENCEG_PlayRndSz(ENT(pev), "HG_CHECK", HTORCH_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); g_fTorchQuestion = 1; break; case 1: // question SENTENCEG_PlayRndSz(ENT(pev), "HG_QUEST", HTORCH_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); g_fTorchQuestion = 2; break; case 2: // statement SENTENCEG_PlayRndSz(ENT(pev), "HG_IDLE", HTORCH_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); break; } } else { switch (g_fTorchQuestion) { case 1: // check in SENTENCEG_PlayRndSz(ENT(pev), "HG_CLEAR", HTORCH_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); break; case 2: // question SENTENCEG_PlayRndSz(ENT(pev), "HG_ANSWER", HTORCH_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); break; } g_fTorchQuestion = 0; } JustSpoke(); } } //========================================================= // CheckAmmo - overridden for the grunt because he actually // uses ammo! (base class doesn't) //========================================================= void CHTorch :: CheckAmmo ( void ) { if ( m_cAmmoLoaded <= 0 ) { SetConditions(bits_COND_NO_AMMO_LOADED); } } //========================================================= // Classify - indicates this monster's place in the // relationship table. //========================================================= int CHTorch :: Classify ( void ) { return CLASS_PLAYER_ALLY; } //========================================================= //========================================================= CBaseEntity *CHTorch :: Kick( void ) { TraceResult tr; UTIL_MakeVectors( pev->angles ); Vector vecStart = pev->origin; vecStart.z += pev->size.z * 0.5f; Vector vecEnd = vecStart + (gpGlobals->v_forward * 70); UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); if ( tr.pHit ) { CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); return pEntity; } return NULL; } //========================================================= // GetGunPosition return the end of the barrel //========================================================= Vector CHTorch :: GetGunPosition( ) //ZZS ????? ???????-??????????????? ??????? ??????? { if (m_fStanding ) { return pev->origin + Vector( 0, 0, 55 ); } else { return pev->origin + Vector( 0, 0, 43 ); } } //========================================================= // Shoot //========================================================= void CHTorch :: Shoot ( void ) { if (m_hEnemy == 0) { return; } Vector vecShootOrigin = GetGunPosition(); Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); UTIL_MakeVectors ( pev->angles ); Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); EjectBrass ( vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL); FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_10DEGREES, 2048, BULLET_PLAYER_357 ); // shoot +-5 degrees pev->effects |= EF_MUZZLEFLASH; m_cAmmoLoaded--;// take away a bullet! Vector angDir = UTIL_VecToAngles( vecShootDir ); SetBlending( 0, angDir.x ); } //========================================================= // AUTOGENE //========================================================= void CHTorch::UpdateGas( void ) { TraceResult tr; Vector posGun, angleGun; Vector vecEndPos; if ( m_pBeam ) { // ALERT ( at_console, "Updated gas\n"); UTIL_MakeVectors( pev->angles ); GetAttachment( 2, posGun, angleGun ); Vector vecEnd = (gpGlobals->v_forward * 5) + posGun; UTIL_TraceLine( posGun, vecEnd, dont_ignore_monsters, edict(), &tr ); if ( tr.flFraction != 1.0f ) { m_pBeam->DoSparks( tr.vecEndPos, posGun ); UTIL_DecalTrace(&tr, DECAL_BIGSHOT1 + RANDOM_LONG(0,4)); MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, tr.vecEndPos ); WRITE_BYTE( TE_STREAK_SPLASH ); WRITE_COORD( tr.vecEndPos.x ); // origin WRITE_COORD( tr.vecEndPos.y ); WRITE_COORD( tr.vecEndPos.z ); WRITE_COORD( tr.vecPlaneNormal.x ); // direction WRITE_COORD( tr.vecPlaneNormal.y ); WRITE_COORD( tr.vecPlaneNormal.z ); WRITE_BYTE( 10 ); // Streak color 6 WRITE_SHORT( 40 ); // count WRITE_SHORT( 25 ); WRITE_SHORT( 50 ); // Random velocity modifier MESSAGE_END(); } //ZZS: ??????????? ????? ? ???????? ???????... //Aperance: ammazzaamaz MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_DLIGHT ); WRITE_COORD( posGun.x ); // origin WRITE_COORD( posGun.y ); WRITE_COORD( posGun.z ); WRITE_BYTE( RANDOM_LONG(4, 16) ); // radius WRITE_BYTE( 251 ); // R WRITE_BYTE( 68 ); // G WRITE_BYTE( 36 ); // B WRITE_BYTE( 1 ); // life * 10 WRITE_BYTE( 0 ); // decay MESSAGE_END(); MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); WRITE_BYTE( TE_ELIGHT ); WRITE_SHORT( entindex( ) + 0x1000 * 3 ); // entity, attachment WRITE_COORD( posGun.x ); // origin WRITE_COORD( posGun.y ); WRITE_COORD( posGun.z ); WRITE_COORD( RANDOM_LONG(8, 12) ); // radius WRITE_BYTE( 251 ); // R WRITE_BYTE( 68 ); // G WRITE_BYTE( 36 ); // B WRITE_BYTE( 1 ); // life * 10 WRITE_COORD( 0 ); // decay MESSAGE_END(); pev->nextthink = gpGlobals->time + 0.1f; } } void CHTorch::MakeGas( void ) { Vector posGun, angleGun; TraceResult tr; Vector vecEndPos; // ALERT ( at_console, "Maked gas\n"); UTIL_MakeVectors( pev->angles ); m_pBeam = CBeam::BeamCreate( g_pModelNameLaser, 7 ); if ( m_pBeam ) { GetAttachment( 4, posGun, angleGun ); GetAttachment( 3, posGun, angleGun ); Vector vecEnd = (gpGlobals->v_forward * 5) + posGun; UTIL_TraceLine( posGun, vecEnd, dont_ignore_monsters, edict(), &tr ); // UTIL_Sparks( tr.vecEndPos ); // UTIL_DecalTrace(&tr, DECAL_BIGSHOT1 + RANDOM_LONG(0,4)); m_pBeam->EntsInit( entindex(), entindex() ); m_pBeam->SetColor( 24, 121, 239 ); m_pBeam->SetBrightness( 190 ); m_pBeam->SetScrollRate( 20 ); m_pBeam->SetStartAttachment( 4 ); m_pBeam->SetEndAttachment( 3 ); m_pBeam->DoSparks( tr.vecEndPos, posGun ); m_pBeam->SetFlags( BEAM_FSHADEIN ); m_pBeam->pev->spawnflags = SF_BEAM_SPARKSTART; } return; } void CHTorch :: KillGas( void ) { if ( m_pBeam ) { UTIL_Remove( m_pBeam ); m_pBeam = NULL; } return; } //========================================================= // HandleAnimEvent - catches the monster-specific messages // that occur when tagged animation frames are played. //========================================================= void CHTorch :: HandleAnimEvent( MonsterEvent_t *pEvent ) { Vector vecShootDir; Vector vecShootOrigin; switch( pEvent->event ) { case HTORCH_AE_SHOWTORCH: pev->body = GUN_NONE; pev->body = GUN_TORCH; break; case HTORCH_AE_SHOWGUN: pev->body = GUN_NONE; pev->body = GUN_EAGLE; break; case HTORCH_AE_HIDETORCH: pev->body = GUN_NONE; break; case HTORCH_AE_ONGAS: MakeGas (); UpdateGas (); break; case HTORCH_AE_OFFGAS: KillGas (); break; case HTORCH_AE_DROP_GUN: { Vector vecGunPos; Vector vecGunAngles; GetAttachment( 0, vecGunPos, vecGunAngles ); // switch to body group with no gun. SetBodygroup( GUN_NONE,GUN_NONE ); // now spawn a gun. if (FBitSet( pev->weapons, HTORCH_EAGLE )) { DropItem( "weapon_eagle", vecGunPos, vecGunAngles ); } else { } } break; case HTORCH_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 HTORCH_AE_BURST1: { if ( FBitSet( pev->weapons, HTORCH_EAGLE )) { 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, "weapons/bullet_hit1.wav", 1, ATTN_NORM ); } else { EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/bullet_hit2.wav", 1, ATTN_NORM ); } } CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); } break; case HTORCH_AE_BURST2: case HTORCH_AE_BURST3: Shoot(); break; case HTORCH_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 HTORCH_AE_CAUGHT_ENEMY: { if ( FOkToSpeak() ) { SENTENCEG_PlayRndSz(ENT(pev), "HG_ALERT", HTORCH_SENTENCE_VOLUME, TORCH_ATTN, 0, m_voicePitch); JustSpoke(); } } break; default: CSquadMonster::HandleAnimEvent( pEvent ); break; } } //========================================================= // Spawn //========================================================= void CHTorch :: Spawn() { Precache( ); SET_MODEL(ENT(pev), "models/hgrunt_torch.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 = 1; 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_IDLE; m_flNextGrenadeCheck = gpGlobals->time + 1; m_flNextPainTime = gpGlobals->time; m_iSentence = HTORCH_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 = HTORCH_EAGLE; } SetBodygroup( GUN_EAGLE, GUN_EAGLE ); m_cClipSize = TORCH_CLIP_SIZE; m_cAmmoLoaded = m_cClipSize; CTalkMonster::g_talkWaitTime = 0; MonsterInit(); // SetUse( &CHTorch :: FollowingUse ); } //========================================================= // Precache - precaches all resources this monster needs //========================================================= void CHTorch :: Precache() { PRECACHE_MODEL("models/hgrunt_torch.mdl"); PRECACHE_SOUND( "weapons/bullet_hit1.wav" ); PRECACHE_SOUND( "weapons/bullet_hit2.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("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event // TalkInit (); // get voice pitch if (RANDOM_LONG(0,1)) m_voicePitch = 109 + RANDOM_LONG(0,7); else m_voicePitch = 100; m_iBrassShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell } /*void CHTorch :: TalkInit() { CSquadMonster::TalkInit(); }*/ //========================================================= // start task //========================================================= void CHTorch :: StartTask ( Task_t *pTask ) { m_iTaskStatus = TASKSTATUS_RUNNING; switch ( pTask->iTask ) { case TASK_TORCH_CHECK_FIRE: if ( !NoFriendlyFire() ) { SetConditions( bits_COND_TORCH_NOFIRE ); } TaskComplete(); break; case TASK_TORCH_SPEAK_SENTENCE: SpeakSentence(); TaskComplete(); break; case TASK_WALK_PATH: case TASK_RUN_PATH: // grunt no longer assumes he is covered if he moves Forget( bits_MEMORY_INCOVER ); CSquadMonster ::StartTask( pTask ); break; case TASK_RELOAD: m_IdealActivity = ACT_RELOAD; break; case TASK_TORCH_FACE_TOSS_DIR: break; case TASK_FACE_IDEAL: case TASK_FACE_ENEMY: CSquadMonster :: StartTask( pTask ); if (pev->movetype == MOVETYPE_FLY) { m_IdealActivity = ACT_GLIDE; } break; default: CSquadMonster :: StartTask( pTask ); break; } } //========================================================= // RunTask //========================================================= void CHTorch :: RunTask ( Task_t *pTask ) { switch ( pTask->iTask ) { case TASK_TORCH_FACE_TOSS_DIR: { // project a point along the toss vector and turn to face that point. MakeIdealYaw( pev->origin + m_vecTossVelocity * 64 ); ChangeYaw( pev->yaw_speed ); if ( FacingIdeal() ) { m_iTaskStatus = TASKSTATUS_COMPLETE; } } break; default: CSquadMonster :: RunTask( pTask ); break; } } //========================================================= // PainSound //========================================================= void CHTorch :: PainSound ( void ) { if ( gpGlobals->time > m_flNextPainTime ) { #if 0 if ( RANDOM_LONG(0,99) < 5 ) { // pain sentences are rare if (FOkToSpeak()) { SENTENCEG_PlayRndSz(ENT(pev), "HG_PAIN", HTORCH_SENTENCE_VOLUME, ATTN_NORM, 0, PITCH_NORM); JustSpoke(); return; } } #endif switch ( RANDOM_LONG(0,6) ) { case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain3.wav", 1, ATTN_NORM ); break; case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain4.wav", 1, ATTN_NORM ); break; case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain5.wav", 1, ATTN_NORM ); break; case 3: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain1.wav", 1, ATTN_NORM ); break; case 4: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain2.wav", 1, ATTN_NORM ); break; } m_flNextPainTime = gpGlobals->time + 1; } } //========================================================= // DeathSound //========================================================= void CHTorch :: DeathSound ( void ) { switch ( RANDOM_LONG(0,2) ) { case 0: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_die1.wav", 1, ATTN_IDLE ); break; case 1: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_die2.wav", 1, ATTN_IDLE ); break; case 2: EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_die3.wav", 1, ATTN_IDLE ); break; } } //========================================================= // AI Schedules Specific to this monster //========================================================= Task_t tlTorchFollow[] = { { TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 128 of target ent (client) { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, }; Schedule_t slTorchFollow[] = { { tlTorchFollow, ARRAYSIZE ( tlTorchFollow ), bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_HEAR_SOUND | bits_COND_PROVOKED, bits_SOUND_DANGER, "Follow" }, }; Task_t tlTorchFaceTarget[] = { { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_FACE_TARGET, (float)0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE }, }; Schedule_t slTorchFaceTarget[] = { { tlTorchFaceTarget, ARRAYSIZE ( tlTorchFaceTarget ), bits_COND_CLIENT_PUSH | bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_HEAR_SOUND | bits_COND_PROVOKED, bits_SOUND_DANGER, "FaceTarget" }, }; Task_t tlIdleTorchStand[] = { { TASK_STOP_MOVING, 0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_WAIT, (float)2 }, // repick IDLESTAND every two seconds. // { TASK_TALK_HEADRESET, (float)0 }, // reset head position }; Schedule_t slIdleTorchStand[] = { { tlIdleTorchStand, ARRAYSIZE ( tlIdleTorchStand ), bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_HEAR_SOUND | bits_COND_SMELL | bits_COND_PROVOKED, bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. //bits_SOUND_PLAYER | //bits_SOUND_WORLD | bits_SOUND_DANGER | bits_SOUND_MEAT |// scents bits_SOUND_CARCASS | bits_SOUND_GARBAGE, "IdleStand" }, }; //========================================================= // TorchFail //========================================================= Task_t tlTorchFail[] = { { TASK_STOP_MOVING, 0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_WAIT, (float)2 }, { TASK_WAIT_PVS, (float)0 }, }; Schedule_t slTorchFail[] = { { tlTorchFail, ARRAYSIZE ( tlTorchFail ), bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1, 0, "Torch Fail" }, }; //========================================================= // Torch Combat Fail //========================================================= Task_t tlTorchCombatFail[] = { { TASK_STOP_MOVING, 0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_WAIT_FACE_ENEMY, (float)2 }, { TASK_WAIT_PVS, (float)0 }, }; Schedule_t slTorchCombatFail[] = { { tlTorchCombatFail, ARRAYSIZE ( tlTorchCombatFail ), bits_COND_CAN_RANGE_ATTACK1, 0, "Torch Combat Fail" }, }; //========================================================= // Victory dance! //========================================================= Task_t tlTorchVictoryDance[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_WAIT, (float)1.5 }, { TASK_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_VICTORY_DANCE }, }; Schedule_t slTorchVictoryDance[] = { { tlTorchVictoryDance, ARRAYSIZE ( tlTorchVictoryDance ), bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE, 0, "GruntVictoryDance" }, }; //========================================================= // Establish line of fire - move to a position that allows // the grunt to attack. //========================================================= Task_t tlTorchEstablishLineOfFire[] = { { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TORCH_ELOF_FAIL }, { TASK_GET_PATH_TO_ENEMY, (float)0 }, { TASK_TORCH_SPEAK_SENTENCE,(float)0 }, { TASK_RUN_PATH, (float)0 }, { TASK_WAIT_FOR_MOVEMENT, (float)0 }, }; Schedule_t slTorchEstablishLineOfFire[] = { { tlTorchEstablishLineOfFire, ARRAYSIZE ( tlTorchEstablishLineOfFire ), bits_COND_NEW_ENEMY | bits_COND_ENEMY_DEAD | bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1 | bits_COND_HEAR_SOUND, bits_SOUND_DANGER, "TorchEstablishLineOfFire" }, }; //========================================================= // TorchFoundEnemy - grunt established sight with an enemy // that was hiding from the squad. //========================================================= Task_t tlTorchFoundEnemy[] = { { TASK_STOP_MOVING, 0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_SIGNAL1 }, }; Schedule_t slTorchFoundEnemy[] = { { tlTorchFoundEnemy, ARRAYSIZE ( tlTorchFoundEnemy ), bits_COND_HEAR_SOUND, bits_SOUND_DANGER, "TorchFoundEnemy" }, }; //========================================================= // TorchCombatFace Schedule //========================================================= Task_t tlTorchCombatFace1[] = { { TASK_STOP_MOVING, 0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_FACE_ENEMY, (float)0 }, { TASK_WAIT, (float)1.5 }, { TASK_SET_SCHEDULE, (float)SCHED_TORCH_SWEEP }, }; Schedule_t slTorchCombatFace[] = { { tlTorchCombatFace1, ARRAYSIZE ( tlTorchCombatFace1 ), bits_COND_NEW_ENEMY | bits_COND_ENEMY_DEAD | bits_COND_CAN_RANGE_ATTACK1, 0, "Combat Face" }, }; //========================================================= // Suppressing fire - don't stop shooting until the clip is // empty or grunt gets hurt. //========================================================= Task_t tlTorchSignalSuppress[] = { { TASK_STOP_MOVING, 0 }, { TASK_FACE_IDEAL, (float)0 }, { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, }; Schedule_t slTorchSignalSuppress[] = { { tlTorchSignalSuppress, ARRAYSIZE ( tlTorchSignalSuppress ), bits_COND_ENEMY_DEAD | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_HEAR_SOUND | bits_COND_TORCH_NOFIRE | bits_COND_NO_AMMO_LOADED, bits_SOUND_DANGER, "SignalSuppress" }, }; Task_t tlTorchSuppress[] = { { TASK_STOP_MOVING, 0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, }; Schedule_t slTorchSuppress[] = { { tlTorchSuppress, ARRAYSIZE ( tlTorchSuppress ), bits_COND_ENEMY_DEAD | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_HEAR_SOUND | bits_COND_TORCH_NOFIRE | bits_COND_NO_AMMO_LOADED, bits_SOUND_DANGER, "Suppress" }, }; //========================================================= // grunt wait in cover - we don't allow danger or the ability // to attack to break a grunt's run to cover schedule, but // when a grunt is in cover, we do want them to attack if they can. //========================================================= Task_t tlTorchWaitInCover[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_SET_ACTIVITY, (float)ACT_IDLE }, { TASK_WAIT_FACE_ENEMY, (float)1 }, }; Schedule_t slTorchWaitInCover[] = { { tlTorchWaitInCover, ARRAYSIZE ( tlTorchWaitInCover ), bits_COND_NEW_ENEMY | bits_COND_HEAR_SOUND | bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1, bits_SOUND_DANGER, "TorchWaitInCover" }, }; //========================================================= // run to cover. // !!!BUGBUG - set a decent fail schedule here. //========================================================= Task_t tlTorchTakeCover1[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TORCH_TAKECOVER_FAILED }, { TASK_WAIT, (float)0.2 }, { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, { TASK_TORCH_SPEAK_SENTENCE, (float)0 }, { TASK_RUN_PATH, (float)0 }, { TASK_WAIT_FOR_MOVEMENT, (float)0 }, { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, { TASK_SET_SCHEDULE, (float)SCHED_TORCH_WAIT_FACE_ENEMY }, }; Schedule_t slTorchTakeCover[] = { { tlTorchTakeCover1, ARRAYSIZE ( tlTorchTakeCover1 ), 0, 0, "TakeCover" }, }; //========================================================= // Torch reload schedule //========================================================= Task_t tlTorchHideReload[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RELOAD }, { 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 }, { TASK_PLAY_SEQUENCE, (float)ACT_RELOAD }, }; Schedule_t slTorchHideReload[] = { { tlTorchHideReload, ARRAYSIZE ( tlTorchHideReload ), bits_COND_HEAVY_DAMAGE | bits_COND_HEAR_SOUND, bits_SOUND_DANGER, "TorchHideReload" } }; //========================================================= // Do a turning sweep of the area //========================================================= Task_t tlTorchSweep[] = { { TASK_TURN_LEFT, (float)179 }, { TASK_WAIT, (float)1 }, { TASK_TURN_LEFT, (float)179 }, { TASK_WAIT, (float)1 }, }; Schedule_t slTorchSweep[] = { { tlTorchSweep, ARRAYSIZE ( tlTorchSweep ), bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_CAN_RANGE_ATTACK1 | bits_COND_HEAR_SOUND, bits_SOUND_WORLD |// sound flags bits_SOUND_DANGER | bits_SOUND_PLAYER, "Torch Sweep" }, }; //========================================================= // primary range attack. Overriden because base class stops attacking when the enemy is occluded. // grunt's grenade toss requires the enemy be occluded. //========================================================= Task_t tlTorchRangeAttack1A[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, }; Schedule_t slTorchRangeAttack1A[] = { { tlTorchRangeAttack1A, ARRAYSIZE ( tlTorchRangeAttack1A ), bits_COND_NEW_ENEMY | bits_COND_ENEMY_DEAD | bits_COND_HEAVY_DAMAGE | bits_COND_ENEMY_OCCLUDED | bits_COND_HEAR_SOUND | bits_COND_TORCH_NOFIRE | bits_COND_NO_AMMO_LOADED, bits_SOUND_DANGER, "Range Attack1A" }, }; //========================================================= // primary range attack. Overriden because base class stops attacking when the enemy is occluded. // grunt's grenade toss requires the enemy be occluded. //========================================================= Task_t tlTorchRangeAttack1B[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_IDLE_ANGRY }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_TORCH_CHECK_FIRE, (float)0 }, { TASK_RANGE_ATTACK1, (float)0 }, }; Schedule_t slTorchRangeAttack1B[] = { { tlTorchRangeAttack1B, ARRAYSIZE ( tlTorchRangeAttack1B ), bits_COND_NEW_ENEMY | bits_COND_ENEMY_DEAD | bits_COND_HEAVY_DAMAGE | bits_COND_ENEMY_OCCLUDED | bits_COND_NO_AMMO_LOADED | bits_COND_TORCH_NOFIRE | bits_COND_HEAR_SOUND, bits_SOUND_DANGER, "Range Attack1B" }, }; //========================================================= // repel //========================================================= Task_t tlTorchRepel[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_FACE_IDEAL, (float)0 }, { TASK_PLAY_SEQUENCE, (float)ACT_GLIDE }, }; Schedule_t slTorchRepel[] = { { tlTorchRepel, ARRAYSIZE ( tlTorchRepel ), bits_COND_SEE_ENEMY | bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_HEAR_SOUND, bits_SOUND_DANGER | bits_SOUND_COMBAT | bits_SOUND_PLAYER, "Repel" }, }; //========================================================= // repel //========================================================= Task_t tlTorchRepelAttack[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_FACE_ENEMY, (float)0 }, { TASK_PLAY_SEQUENCE, (float)ACT_FLY }, }; Schedule_t slTorchRepelAttack[] = { { tlTorchRepelAttack, ARRAYSIZE ( tlTorchRepelAttack ), bits_COND_ENEMY_OCCLUDED, 0, "Repel Attack" }, }; //========================================================= // repel land //========================================================= Task_t tlTorchRepelLand[] = { { TASK_STOP_MOVING, (float)0 }, { TASK_PLAY_SEQUENCE, (float)ACT_LAND }, { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, { TASK_RUN_PATH, (float)0 }, { TASK_WAIT_FOR_MOVEMENT, (float)0 }, { TASK_CLEAR_LASTPOSITION, (float)0 }, }; Schedule_t slTorchRepelLand[] = { { tlTorchRepelLand, ARRAYSIZE ( tlTorchRepelLand ), bits_COND_SEE_ENEMY | bits_COND_NEW_ENEMY | bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE | bits_COND_HEAR_SOUND, bits_SOUND_DANGER | bits_SOUND_COMBAT | bits_SOUND_PLAYER, "Repel Land" }, }; DEFINE_CUSTOM_SCHEDULES( CHTorch ) { slTorchFail, slTorchCombatFail, slTorchVictoryDance, slTorchEstablishLineOfFire, slTorchFoundEnemy, slTorchCombatFace, slTorchSignalSuppress, slTorchSuppress, slTorchWaitInCover, slTorchTakeCover, slTorchHideReload, slTorchSweep, slTorchRangeAttack1A, slTorchRangeAttack1B, slTorchRepel, slTorchRepelAttack, slTorchRepelLand, slTorchFollow, slTorchFaceTarget, slIdleTorchStand, }; IMPLEMENT_CUSTOM_SCHEDULES( CHTorch, CSquadMonster ); //========================================================= // SetActivity //========================================================= void CHTorch :: SetActivity ( Activity NewActivity ) { int iSequence = ACTIVITY_NOT_AVAILABLE; void *pmodel = GET_MODEL_PTR( ENT(pev) ); switch ( NewActivity) { case ACT_RANGE_ATTACK1: // grunt is either shooting standing or shooting crouched if (FBitSet( pev->weapons, HTORCH_EAGLE)) { if ( m_fStanding ) { // get aimable sequence iSequence = LookupSequence( "standing_mp5" ); } else { // get crouching shoot iSequence = LookupSequence( "crouching_mp5" ); } } break; case ACT_RUN: if ( pev->health <= HTORCH_LIMP_HEALTH ) { // limp! iSequence = LookupActivity ( ACT_RUN_HURT ); } else { iSequence = LookupActivity ( NewActivity ); } break; case ACT_WALK: if ( pev->health <= HTORCH_LIMP_HEALTH ) { // limp! iSequence = LookupActivity ( ACT_WALK_HURT ); } else { iSequence = LookupActivity ( NewActivity ); } break; case ACT_IDLE: if ( m_MonsterState == MONSTERSTATE_COMBAT ) { NewActivity = ACT_IDLE_ANGRY; } iSequence = LookupActivity ( NewActivity ); break; default: iSequence = LookupActivity ( NewActivity ); break; } m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present // Set to the desired anim, or default anim if the desired is not present if ( iSequence > ACTIVITY_NOT_AVAILABLE ) { if ( pev->sequence != iSequence || !m_fSequenceLoops ) { pev->frame = 0; } pev->sequence = iSequence; // Set to the reset anim (if it's there) ResetSequenceInfo( ); SetYawSpeed(); } else { // Not available try to get default anim ALERT ( at_console, "%s has no sequence for act:%d\n", STRING(pev->classname), NewActivity ); pev->sequence = 0; // Set to the reset anim (if it's there) } } //========================================================= // Get Schedule! //========================================================= Schedule_t *CHTorch :: GetSchedule( void ) { // clear old sentence m_iSentence = HTORCH_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_TORCH_REPEL_LAND ); } else { // repel down a rope, if ( m_MonsterState == MONSTERSTATE_COMBAT ) return GetScheduleOfType ( SCHED_TORCH_REPEL_ATTACK ); else return GetScheduleOfType ( SCHED_TORCH_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), "HG_GREN", HTORCH_SENTENCE_VOLUME, TORCH_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), "HG_ALERT", HTORCH_SENTENCE_VOLUME, TORCH_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), "HG_MONST", HTORCH_SENTENCE_VOLUME, TORCH_ATTN, 0, m_voicePitch); JustSpoke(); } if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) { return GetScheduleOfType ( SCHED_TORCH_SUPPRESS ); } else { return GetScheduleOfType ( SCHED_TORCH_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_TORCH_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), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); m_iSentence = HTORCH_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 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_TORCH_FOUND_ENEMY ); } } if ( OccupySlot ( bits_SLOTS_HGRUNT_ENGAGE ) ) { // try to take an available ENGAGE slot return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); } else { // hide! return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); } } // can't see enemy else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) { 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), "HG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); m_iSentence = HTORCH_SENT_CHARGE; //JustSpoke(); } return GetScheduleOfType( SCHED_TORCH_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), "HG_TAUNT", HTORCH_SENTENCE_VOLUME, TORCH_ATTN, 0, m_voicePitch); JustSpoke(); } return GetScheduleOfType( SCHED_STANDOFF ); } } if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) { return GetScheduleOfType ( SCHED_TORCH_ESTABLISH_LINE_OF_FIRE ); } break; case MONSTERSTATE_ALERT: case MONSTERSTATE_IDLE: if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) { // flinch if hurt return GetScheduleOfType( SCHED_SMALL_FLINCH ); } // if ( m_hEnemy == 0 && IsFollowing() ) { if ( !m_hTargetEnt->IsAlive() ) { // UNDONE: Comment about the recently dead player here? StopFollowing( FALSE ); break; } else { if ( HasConditions( bits_COND_CLIENT_PUSH ) ) { return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); } return GetScheduleOfType( SCHED_TARGET_FACE ); } } if ( HasConditions( bits_COND_CLIENT_PUSH ) ) { return GetScheduleOfType( SCHED_MOVE_AWAY ); } // TrySmellTalk(); // break; } default: break; } // no special cases here, call the base class return CSquadMonster :: GetSchedule(); } MONSTERSTATE CHTorch :: GetIdealState ( void ) { return CSquadMonster::GetIdealState(); } void CHTorch::DeclineFollowing( void ) { // PlaySentence( "BA_POK", 2, VOL_NORM, ATTN_NORM ); } //========================================================= //========================================================= Schedule_t* CHTorch :: GetScheduleOfType ( int Type ) { Schedule_t *psched; switch ( Type ) { case SCHED_TAKE_COVER_FROM_ENEMY: { { if ( RANDOM_LONG(0,1) ) { return &slTorchTakeCover[ 0 ]; } } } case SCHED_TORCH_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_TORCH_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_TORCH_ESTABLISH_LINE_OF_FIRE: { return &slTorchEstablishLineOfFire[ 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 &slTorchRangeAttack1B[ 0 ]; else return &slTorchRangeAttack1A[ 0 ]; } case SCHED_COMBAT_FACE: { return &slTorchCombatFace[ 0 ]; } case SCHED_TORCH_WAIT_FACE_ENEMY: { return &slTorchWaitInCover[ 0 ]; } case SCHED_TORCH_SWEEP: { return &slTorchSweep[ 0 ]; } case SCHED_TORCH_COVER_AND_RELOAD: { return &slTorchHideReload[ 0 ]; } case SCHED_TORCH_FOUND_ENEMY: { return &slTorchFoundEnemy[ 0 ]; } case SCHED_VICTORY_DANCE: { if ( InSquad() ) { if ( !IsLeader() ) { return &slTorchFail[ 0 ]; } } return &slTorchVictoryDance[ 0 ]; } case SCHED_TORCH_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 &slTorchSignalSuppress[ 0 ]; } else { return &slTorchSuppress[ 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 &slTorchCombatFail[ 0 ]; } return &slTorchFail[ 0 ]; } case SCHED_TORCH_REPEL: { if (pev->velocity.z > -128) pev->velocity.z -= 32; return &slTorchRepel[ 0 ]; } case SCHED_TORCH_REPEL_ATTACK: { if (pev->velocity.z > -128) pev->velocity.z -= 32; return &slTorchRepelAttack[ 0 ]; } case SCHED_TORCH_REPEL_LAND: { return &slTorchRepelLand[ 0 ]; } case SCHED_TARGET_FACE: { // call base class default so that barney will talk // when 'used' psched = CSquadMonster::GetScheduleOfType(Type); if (psched == slIdleStand) return slTorchFaceTarget; // override this for different target face behavior else return psched; } case SCHED_TARGET_CHASE: { return slTorchFollow; } case SCHED_IDLE_STAND: { // call base class default so that scientist will talk // when standing during idle psched = CSquadMonster::GetScheduleOfType(Type); if (psched == slIdleStand) { // just look straight ahead. return slIdleTorchStand; } else return psched; } default: { return CSquadMonster :: GetScheduleOfType ( Type ); } } } //========================================================= // CHTorchRepel - when triggered, spawns a monster_human_grunt // repelling down a line. //========================================================= class CHTorchRepel : 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_torch_repel, CHTorchRepel ); void CHTorchRepel::Spawn( void ) { Precache( ); pev->solid = SOLID_NOT; SetUse( &CHTorchRepel::RepelUse ); } void CHTorchRepel::Precache( void ) { UTIL_PrecacheOther( "monster_human_torch" ); m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); } void CHTorchRepel::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); /* if ( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP) return NULL; */ CBaseEntity *pEntity = Create( "monster_human_torch", pev->origin, pev->angles ); CBaseMonster *pTorch = pEntity->MyMonsterPointer( ); pTorch->pev->movetype = MOVETYPE_FLY; pTorch->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); pTorch->SetActivity( ACT_GLIDE ); // UNDONE: position? pTorch->m_vecLastPosition = tr.vecEndPos; CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); pBeam->PointEntInit( pev->origin + Vector(0,0,112), pTorch->entindex() ); pBeam->SetFlags( BEAM_FSOLID ); pBeam->SetColor( 255, 255, 255 ); pBeam->SetThink( &CBaseEntity::SUB_Remove ); pBeam->pev->nextthink = gpGlobals->time + -4096.0f * tr.flFraction / pTorch->pev->velocity.z + 0.5f; UTIL_Remove( this ); } //========================================================= // DEAD HGRUNT PROP //========================================================= class CDeadHTorch : public CBaseMonster { public: void Spawn( void ); int Classify ( void ) { return CLASS_PLAYER_ALLY; } 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 *CDeadHTorch::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; void CDeadHTorch::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_htorch_dead, CDeadHTorch ); //========================================================= // ********** DeadHTorch SPAWN ********** //========================================================= void CDeadHTorch :: Spawn( void ) { PRECACHE_MODEL("models/hgrunt_torch.mdl"); SET_MODEL(ENT(pev), "models/hgrunt_torch.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 htorch with bad pose\n" ); } // Corpses have less health pev->health = 8; // map old bodies onto new bodies switch( pev->body ) { case 0: // Grunt with Gun pev->body = 0; pev->skin = 0; SetBodygroup( HEAD_TORCH, HEAD_TORCH ); SetBodygroup( GUN_NONE, GUN_EAGLE ); break; case 1: // Grunt no Gun pev->body = 0; pev->skin = 0; SetBodygroup( HEAD_TORCH, HEAD_TORCH ); SetBodygroup( GUN_NONE, GUN_NONE ); break; } MonsterInitDead(); }