diff --git a/dlls/clcampaign/bigsmoke.cpp b/dlls/clcampaign/bigsmoke.cpp index d0d21a60..f236879d 100644 --- a/dlls/clcampaign/bigsmoke.cpp +++ b/dlls/clcampaign/bigsmoke.cpp @@ -1,245 +1,638 @@ -/*** -* -* 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. -* -****/ -//========================================================= -// Alien slave monster -//========================================================= - #include "extdll.h" +#include "plane.h" #include "util.h" #include "cbase.h" #include "monsters.h" -#include "squadmonster.h" #include "schedule.h" -#include "effects.h" +#include "animation.h" +#include "squadmonster.h" #include "weapons.h" +#include "talkmonster.h" #include "soundent.h" +#include "effects.h" +#include "customentity.h" extern DLL_GLOBAL int g_iSkillLevel; +#define GRUNT_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! +#define GRUNT_VOL 0.35 // volume of grunt sounds +#define GRUNT_ATTN ATTN_NORM // attenutation of grunt sentences +#define HSMOKE_LIMP_HEALTH 20 +#define HSMOKE_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot. +#define HSMOKE_NUM_HEADS 2 // how many grunt heads are there? +#define HSMOKE_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill +#define HSMOKE_SENTENCE_VOLUME (float)0.6 // volume of grunt sentences + +#define HSMOKE_9MMAR ( 1 << 0) +#define HSMOKE_HANDGRENADE ( 1 << 1) +#define HSMOKE_GRENADELAUNCHER ( 1 << 2) +#define HSMOKE_SHOTGUN ( 1 << 3) + +#define HEAD_GROUP 1 +#define HEAD_GRUNT 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 ISLAVE_AE_CLAW ( 1 ) -#define ISLAVE_AE_CLAWRAKE ( 2 ) -#define ISLAVE_AE_ZAP_POWERUP ( 3 ) -#define ISLAVE_AE_ZAP_SHOOT ( 4 ) -#define ISLAVE_AE_ZAP_DONE ( 5 ) +#define HSMOKE_AE_RELOAD ( 2 ) +#define HSMOKE_AE_KICK ( 3 ) +#define HSMOKE_AE_BURST1 ( 4 ) +#define HSMOKE_AE_BURST2 ( 5 ) +#define HSMOKE_AE_BURST3 ( 6 ) +#define HSMOKE_AE_GREN_TOSS ( 7 ) +#define HSMOKE_AE_GREN_LAUNCH ( 8 ) +#define HSMOKE_AE_GREN_DROP ( 9 ) +#define HSMOKE_AE_CAUGHT_ENEMY ( 10 ) // grunt established sight with an enemy (player only) that had previously eluded the squad. +#define HSMOKE_AE_DROP_GUN ( 11 ) // grunt (probably dead) is dropping his mp5. -#define ISLAVE_MAX_BEAMS 8 +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_GRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, + SCHED_GRUNT_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_GRUNT_COVER_AND_RELOAD, + SCHED_GRUNT_SWEEP, + SCHED_GRUNT_FOUND_ENEMY, + SCHED_GRUNT_REPEL, + SCHED_GRUNT_REPEL_ATTACK, + SCHED_GRUNT_REPEL_LAND, + SCHED_GRUNT_WAIT_FACE_ENEMY, + SCHED_GRUNT_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. + SCHED_GRUNT_ELOF_FAIL +}; -class CBigSmoke : public CSquadMonster +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_GRUNT_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, + TASK_GRUNT_SPEAK_SENTENCE, + TASK_GRUNT_CHECK_FIRE +}; + +//========================================================= +// monster-specific conditions +//========================================================= +#define bits_COND_GRUNT_NOFIRE ( bits_COND_SPECIAL1 ) + +class CSmoke : public CSquadMonster { public: void Spawn( void ); void Precache( void ); void SetYawSpeed( void ); - int ISoundMask( void ); int Classify( void ); - int IRelationship( CBaseEntity *pTarget ); + int ISoundMask( void ); void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL FCanCheckAttacks( void ); + BOOL CheckMeleeAttack1( float flDot, float flDist ); BOOL CheckRangeAttack1( float flDot, float flDist ); BOOL CheckRangeAttack2( float flDot, float flDist ); - void CallForHelp( char *szClassname, float flDist, EHANDLE hEnemy, Vector &vecLocation ); - 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 IdleSound( void ); - - void Killed( entvars_t *pevAttacker, int iGib ); - + void CheckAmmo( void ); + void SetActivity( Activity NewActivity ); void StartTask( Task_t *pTask ); - Schedule_t *GetSchedule( void ); - Schedule_t *GetScheduleOfType( int Type ); - CUSTOM_SCHEDULES + void RunTask( Task_t *pTask ); + Vector GetGunPosition( void ); + void Shoot( void ); + void Shotgun( void ); + void PrescheduleThink( void ); + void GibMonster( void ); + void SpeakSentence( void ); int Save( CSave &save ); int Restore( CRestore &restore ); + + CBaseEntity *Kick( void ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType( int Type ); + 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 ); + + int IRelationship( CBaseEntity *pTarget ); + + BOOL FOkToSpeak( void ); + void JustSpoke( void ); + + CUSTOM_SCHEDULES static TYPEDESCRIPTION m_SaveData[]; - void ClearBeams(); - void ArmBeam( int side ); - void WackBeam( int side, CBaseEntity *pEntity ); - void ZapBeam( int side ); - void BeamGlow( void ); + // checking the feasibility of a grenade toss is kind of costly, so we do it every couple of seconds, + // not every server frame. + float m_flNextGrenadeCheck; + float m_flNextPainTime; + float m_flLastEnemySightTime; - int m_iBravery; + Vector m_vecTossVelocity; - CBeam *m_pBeam[ISLAVE_MAX_BEAMS]; - - int m_iBeams; - float m_flNextAttack; + 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; - EHANDLE m_hDead; + int m_iBrassShell; + int m_iShotgunShell; - static const char *pAttackHitSounds[]; - static const char *pAttackMissSounds[]; - static const char *pPainSounds[]; - static const char *pDeathSounds[]; + int m_iSentence; + + static const char *pGruntSentences[]; }; -LINK_ENTITY_TO_CLASS( monster_big_smoke, CBigSmoke ) +LINK_ENTITY_TO_CLASS( monster_big_smoke, CSmoke ) -TYPEDESCRIPTION CBigSmoke::m_SaveData[] = +TYPEDESCRIPTION CSmoke::m_SaveData[] = { - DEFINE_FIELD( CBigSmoke, m_iBravery, FIELD_INTEGER ), - - DEFINE_ARRAY( CBigSmoke, m_pBeam, FIELD_CLASSPTR, ISLAVE_MAX_BEAMS ), - DEFINE_FIELD( CBigSmoke, m_iBeams, FIELD_INTEGER ), - DEFINE_FIELD( CBigSmoke, m_flNextAttack, FIELD_TIME ), - - DEFINE_FIELD( CBigSmoke, m_voicePitch, FIELD_INTEGER ), - - DEFINE_FIELD( CBigSmoke, m_hDead, FIELD_EHANDLE ), - + DEFINE_FIELD( CSmoke, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CSmoke, m_flNextPainTime, FIELD_TIME ), + //DEFINE_FIELD( CSmoke, m_flLastEnemySightTime, FIELD_TIME ), // don't save, go to zero + DEFINE_FIELD( CSmoke, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CSmoke, m_fThrowGrenade, FIELD_BOOLEAN ), + DEFINE_FIELD( CSmoke, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CSmoke, m_fFirstEncounter, FIELD_BOOLEAN ), + DEFINE_FIELD( CSmoke, m_cClipSize, FIELD_INTEGER ), + DEFINE_FIELD( CSmoke, m_voicePitch, FIELD_INTEGER ), + //DEFINE_FIELD( CShotgun, m_iBrassShell, FIELD_INTEGER ), + //DEFINE_FIELD( CShotgun, m_iShotgunShell, FIELD_INTEGER ), + DEFINE_FIELD( CSmoke, m_iSentence, FIELD_INTEGER ), }; -IMPLEMENT_SAVERESTORE( CBigSmoke, CSquadMonster ) +IMPLEMENT_SAVERESTORE( CSmoke, CSquadMonster ) -const char *CBigSmoke::pAttackHitSounds[] = +const char *CSmoke::pGruntSentences[] = { - "bigsmoke/fire1.wav" + "SM_GREN", // grenade scared grunt + "SM_ALERT", // sees player + "SM_MONSTER", // sees monster + "SM_COVER", // running to cover + "SM_THROW", // about to throw grenade + "SM_CHARGE", // running out to get the enemy + "SM_TAUNT", // say rude things }; -const char *CBigSmoke::pAttackMissSounds[] = +typedef enum { - "bigsmoke/fire1.wav", -}; -const char *CBigSmoke::pPainSounds[] = -{ - "aslave/slv_null.wav", -}; - -const char *CBigSmoke::pDeathSounds[] = -{ - "aslave/slv_null.wav", - "aslave/slv_null.wav", -}; + HSMOKE_SENT_NONE = -1, + HSMOKE_SENT_GREN = 0, + HSMOKE_SENT_ALERT, + HSMOKE_SENT_MONSTER, + HSMOKE_SENT_COVER, + HSMOKE_SENT_THROW, + HSMOKE_SENT_CHARGE, + HSMOKE_SENT_TAUNT +} HSMOKE_SENTENCE_TYPES; //========================================================= -// Classify - indicates this monster's place in the -// relationship table. +// 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. //========================================================= -int CBigSmoke::Classify( void ) +void CSmoke::SpeakSentence( void ) { - return CLASS_ALIEN_MILITARY; -} - -int CBigSmoke::IRelationship( CBaseEntity *pTarget ) -{ - if( ( pTarget->IsPlayer() ) ) - if( ( pev->spawnflags & SF_MONSTER_WAIT_UNTIL_PROVOKED ) && ! ( m_afMemory & bits_MEMORY_PROVOKED ) ) - return R_NO; - return CBaseMonster::IRelationship( pTarget ); -} - -void CBigSmoke::CallForHelp( char *szClassname, float flDist, EHANDLE hEnemy, Vector &vecLocation ) -{ - // ALERT( at_aiconsole, "help " ); - - // skip ones not on my netname - if( FStringNull( pev->netname ) ) - return; - - CBaseEntity *pEntity = NULL; - - while( ( pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ) ) ) != NULL) + if( m_iSentence == HSMOKE_SENT_NONE ) { - float d = ( pev->origin - pEntity->pev->origin ).Length(); - if( d < flDist ) + // no sentence cued up. + return; + } + + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), pGruntSentences[m_iSentence], HSMOKE_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } +} + +//========================================================= +// IRelationship - overridden because Alien Grunts are +// Human Grunt's nemesis. +//========================================================= +int CSmoke::IRelationship( CBaseEntity *pTarget ) +{ + if( FClassnameIs( pTarget->pev, "monster_alien_Smoke" ) || ( FClassnameIs( pTarget->pev, "monster_gargantua" ) ) ) + { + return R_NM; + } + + return CSquadMonster::IRelationship( pTarget ); +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CSmoke::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, HSMOKE_SHOTGUN ) ) { - CBaseMonster *pMonster = pEntity->MyMonsterPointer(); - if( pMonster ) + pGun = DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else + { + pGun = DropItem( "weapon_9mmAR", 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, HSMOKE_GRENADELAUNCHER ) ) + { + pGun = DropItem( "ammo_ARgrenades", vecGunPos, vecGunAngles ); + if ( pGun ) { - pMonster->m_afMemory |= bits_MEMORY_PROVOKED; - pMonster->PushEnemy( hEnemy, vecLocation ); + 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(); +} + +//========================================================= +// ISoundMask - Overidden for human grunts because they +// hear the DANGER sound that is made by hand grenades and +// other dangerous items. +//========================================================= +int CSmoke::ISoundMask( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_DANGER; +} + +//========================================================= +// someone else is talking - don't speak +//========================================================= +BOOL CSmoke::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 CSmoke::JustSpoke( void ) +{ + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT( 1.5f, 2.0f ); + m_iSentence = HSMOKE_SENT_NONE; +} + +//========================================================= +// PrescheduleThink - this function runs after conditions +// are collected and before scheduling code is run. +//========================================================= +void CSmoke::PrescheduleThink( void ) +{ + 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.0f ) + { + // been a while since we've seen the enemy + MySquadLeader()->m_fEnemyEluded = TRUE; } } } } - -void CBigSmoke::IdleSound( void ) +//========================================================= +// 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 CSmoke::FCanCheckAttacks( void ) { - if( RANDOM_LONG( 0, 2 ) == 0 ) + if( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) { - SENTENCEG_PlayRndSz( ENT( pev ), "SLV_IDLE", 0.85, ATTN_NORM, 0, m_voicePitch ); + return TRUE; + } + else + { + return FALSE; } -#if 0 - int side = RANDOM_LONG( 0, 1 ) * 2 - 1; - - ClearBeams(); - ArmBeam( side ); - - UTIL_MakeAimVectors( pev->angles ); - Vector vecSrc = pev->origin + gpGlobals->v_right * 2 * side; - MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); - WRITE_BYTE( TE_DLIGHT ); - WRITE_COORD( vecSrc.x ); // X - WRITE_COORD( vecSrc.y ); // Y - WRITE_COORD( vecSrc.z ); // Z - WRITE_BYTE( 8 ); // radius * 0.1 - WRITE_BYTE( 255 ); // r - WRITE_BYTE( 180 ); // g - WRITE_BYTE( 96 ); // b - WRITE_BYTE( 10 ); // time * 10 - WRITE_BYTE( 0 ); // decay * 0.1 - MESSAGE_END(); - - EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, "debris/flesh1.wav", 1, ATTN_NORM, 0, 100 ); -#endif } - //========================================================= -// ISoundMask - returns a bit mask indicating which types -// of sounds this monster regards. +// CheckMeleeAttack1 //========================================================= -int CBigSmoke::ISoundMask( void ) +BOOL CSmoke::CheckMeleeAttack1( float flDot, float flDist ) { - return bits_SOUND_WORLD | - bits_SOUND_COMBAT | - bits_SOUND_DANGER | - bits_SOUND_PLAYER; + CBaseMonster *pEnemy = 0; + + if( m_hEnemy != 0 ) + { + pEnemy = m_hEnemy->MyMonsterPointer(); + + if( !pEnemy ) + { + return FALSE; + } + + if( flDist <= 64.0f && flDot >= 0.7f && + pEnemy->Classify() != CLASS_ALIEN_BIOWEAPON && + pEnemy->Classify() != CLASS_PLAYER_BIOWEAPON ) + { + return TRUE; + } + } + return FALSE; } -void CBigSmoke::Killed( entvars_t *pevAttacker, int iGib ) +//========================================================= +// CheckRangeAttack1 - overridden for HSmoke, cause +// FCanCheckAttacks() doesn't disqualify all attacks based +// on whether or not the enemy is occluded because unlike +// the base class, the HSmoke 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 CSmoke::CheckRangeAttack1( float flDot, float flDist ) { - ClearBeams(); - CSquadMonster::Killed( pevAttacker, iGib ); + if( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 2048.0f && 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; +} + +//========================================================= +// CheckRangeAttack2 - this checks the Grunt's grenade +// attack. +//========================================================= +BOOL CSmoke::CheckRangeAttack2( float flDot, float flDist ) +{ + if( !FBitSet( pev->weapons, ( HSMOKE_HANDGRENADE | HSMOKE_GRENADELAUNCHER ) ) ) + { + return FALSE; + } + + // if the grunt isn't moving, it's ok to check. + if( m_flGroundSpeed != 0 ) + { + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + // assume things haven't changed too much since last time + if( gpGlobals->time < m_flNextGrenadeCheck ) + { + return m_fThrowGrenade; + } + + if( !FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) && m_hEnemy->pev->waterlevel == 0 && m_vecEnemyLKP.z > pev->absmax.z ) + { + //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to + // be grenaded. + // don't throw grenades at anything that isn't on the ground! + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + Vector vecTarget; + + if( FBitSet( pev->weapons, HSMOKE_HANDGRENADE ) ) + { + // find feet + if( RANDOM_LONG( 0, 1 ) ) + { + // magically know where they are + vecTarget = Vector( m_hEnemy->pev->origin.x, m_hEnemy->pev->origin.y, m_hEnemy->pev->absmin.z ); + } + else + { + // toss it to where you last saw them + vecTarget = m_vecEnemyLKP; + } + // vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); + // estimate position + // vecTarget = vecTarget + m_hEnemy->pev->velocity * 2; + } + else + { + // find target + // vecTarget = m_hEnemy->BodyTarget( pev->origin ); + vecTarget = m_vecEnemyLKP + ( m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin ); + // estimate position + if( HasConditions( bits_COND_SEE_ENEMY ) ) + vecTarget = vecTarget + ( ( vecTarget - pev->origin).Length() / gSkillData.hgruntGrenadeSpeed ) * m_hEnemy->pev->velocity; + } + + // are any of my squad members near the intended grenade impact area? + if( InSquad() ) + { + if( SquadMemberInRange( vecTarget, 256 ) ) + { + // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + } + } + + if( ( vecTarget - pev->origin ).Length2D() <= 256.0f ) + { + // crap, I don't want to blow myself up + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + if( FBitSet( pev->weapons, HSMOKE_HANDGRENADE ) ) + { + Vector vecToss = VecCheckToss( pev, GetGunPosition(), vecTarget, 0.5 ); + + if( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time; // 1/3 second. + } + else + { + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1.0f; // one full second. + } + } + else + { + Vector vecToss = VecCheckThrow( pev, GetGunPosition(), vecTarget, gSkillData.hgruntGrenadeSpeed, 0.5 ); + + if( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 0.3f; // 1/3 second. + } + else + { + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1.0f; // one full second. + } + } + + return m_fThrowGrenade; +} + +//========================================================= +// TraceAttack - make sure we're not taking it in the helmet +//========================================================= +void CSmoke::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_GRUNT && ( bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB ) ) ) + { + // absorb damage + flDamage -= 20; + if( flDamage <= 0 ) + { + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + flDamage = 0.01f; + } + } + // it's head shot anyways + ptr->iHitgroup = HITGROUP_HEAD; + } + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + +//========================================================= +// 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 CSmoke::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + 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 CBigSmoke::SetYawSpeed( void ) +void CSmoke::SetYawSpeed( void ) { int ys; switch( m_Activity ) { - case ACT_WALK: - ys = 50; + case ACT_IDLE: + ys = 150; break; - case ACT_RUN: - ys = 70; + case ACT_RUN: + ys = 150; break; - case ACT_IDLE: - ys = 50; + case ACT_WALK: + ys = 180; + break; + case ACT_RANGE_ATTACK1: + ys = 120; + break; + case ACT_RANGE_ATTACK2: + ys = 120; + break; + case ACT_MELEE_ATTACK1: + ys = 120; + break; + case ACT_MELEE_ATTACK2: + 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; @@ -249,137 +642,235 @@ void CBigSmoke::SetYawSpeed( void ) pev->yaw_speed = ys; } + +//========================================================= +// CheckAmmo - overridden for the grunt because he actually +// uses ammo! (base class doesn't) +//========================================================= +void CSmoke::CheckAmmo( void ) +{ + if( m_cAmmoLoaded <= 0 ) + { + SetConditions( bits_COND_NO_AMMO_LOADED ); + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CSmoke::Classify( void ) +{ + return CLASS_HUMAN_MILITARY; +} + +//========================================================= +//========================================================= +CBaseEntity *CSmoke::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 CSmoke::GetGunPosition() +{ + if( m_fStanding ) + { + return pev->origin + Vector( 0, 0, 60 ); + } + else + { + return pev->origin + Vector( 0, 0, 48 ); + } +} + +//========================================================= +// Shoot +//========================================================= +void CSmoke::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_MONSTER_MP5 ); // shoot +-5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + +//========================================================= +// Shoot +//========================================================= +void CSmoke::Shotgun( 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_iShotgunShell, TE_BOUNCE_SHOTSHELL ); + FireBullets( gSkillData.hgruntShotgunPellets, vecShootOrigin, vecShootDir, VECTOR_CONE_15DEGREES, 2048, BULLET_PLAYER_BUCKSHOT, 0 ); // shoot +-7.5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + //========================================================= // HandleAnimEvent - catches the monster-specific messages // that occur when tagged animation frames are played. -// -// Returns number of events handled, 0 if none. //========================================================= -void CBigSmoke::HandleAnimEvent( MonsterEvent_t *pEvent ) +void CSmoke::HandleAnimEvent( MonsterEvent_t *pEvent ) { - // ALERT( at_console, "event %d : %f\n", pEvent->event, pev->frame ); + Vector vecShootDir; + Vector vecShootOrigin; + switch( pEvent->event ) { - case ISLAVE_AE_CLAW: + case HSMOKE_AE_DROP_GUN: { - // SOUND HERE! - CBaseEntity *pHurt = CheckTraceHullAttack( 71, 1, DMG_BULLET ); - if( pHurt ) + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( GUN_GROUP, GUN_NONE ); + + // now spawn a gun. + if( FBitSet( pev->weapons, HSMOKE_SHOTGUN ) ) { - if( pHurt->pev->flags & ( FL_MONSTER | FL_CLIENT ) ) - { - pHurt->pev->punchangle.z = -18; - pHurt->pev->punchangle.x = 5; - } - // Play a random attack hit sound - EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, pAttackHitSounds[RANDOM_LONG( 0, ARRAYSIZE( pAttackHitSounds ) - 1 )], 1.0, ATTN_NORM, 0, m_voicePitch ); + DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); } 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, m_voicePitch ); + DropItem( "weapon_9mmAR", vecGunPos, vecGunAngles ); + } + + if( FBitSet( pev->weapons, HSMOKE_GRENADELAUNCHER ) ) + { + DropItem( "ammo_ARgrenades", BodyTarget( pev->origin ), vecGunAngles ); } } break; - case ISLAVE_AE_CLAWRAKE: + case HSMOKE_AE_RELOAD: + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "hSmoke/gr_reload1.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions( bits_COND_NO_AMMO_LOADED ); + break; + case HSMOKE_AE_GREN_TOSS: { - CBaseEntity *pHurt = CheckTraceHullAttack( 70, 1, DMG_BULLET ); - if( pHurt ) - { - if( pHurt->pev->flags & ( FL_MONSTER | FL_CLIENT ) ) - { - pHurt->pev->punchangle.z = -18; - pHurt->pev->punchangle.x = 5; - } - EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, pAttackHitSounds[RANDOM_LONG( 0, ARRAYSIZE( pAttackHitSounds ) - 1 )], 1.0, ATTN_NORM, 0, m_voicePitch ); - } - else - { - EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, pAttackMissSounds[RANDOM_LONG( 0, ARRAYSIZE( pAttackMissSounds ) - 1 )], 1.0, ATTN_NORM, 0, m_voicePitch ); - } + 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 ISLAVE_AE_ZAP_POWERUP: + case HSMOKE_AE_GREN_LAUNCH: { - // speed up attack when on hard + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM ); + CGrenade::ShootContact( pev, GetGunPosition(), m_vecTossVelocity ); + m_fThrowGrenade = FALSE; if( g_iSkillLevel == SKILL_HARD ) - pev->framerate = 1.5; - - UTIL_MakeAimVectors( pev->angles ); - - if( m_iBeams == 0 ) + m_flNextGrenadeCheck = gpGlobals->time + RANDOM_FLOAT( 2.0f, 5.0f );// wait a random amount of time before shooting again + else + m_flNextGrenadeCheck = gpGlobals->time + 6.0f;// wait six seconds before even looking again to see if a grenade can be thrown. + } + break; + case HSMOKE_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 HSMOKE_AE_BURST1: + { + if( FBitSet( pev->weapons, HSMOKE_9MMAR ) ) { - Vector vecSrc = pev->origin + gpGlobals->v_forward * 2; - MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); - WRITE_BYTE( TE_DLIGHT ); - WRITE_COORD( vecSrc.x ); // X - WRITE_COORD( vecSrc.y ); // Y - WRITE_COORD( vecSrc.z ); // Z - WRITE_BYTE( 12 ); // radius * 0.1 - WRITE_BYTE( 255 ); // r - WRITE_BYTE( 180 ); // g - WRITE_BYTE( 96 ); // b - WRITE_BYTE( 20 / pev->framerate ); // time * 10 - WRITE_BYTE( 0 ); // decay * 0.1 - MESSAGE_END(); - } - if( m_hDead != NULL ) - { - WackBeam( -1, m_hDead ); - WackBeam( 1, m_hDead ); + Shoot(); + + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "bigsmoke/fire1.wav", 1, ATTN_NORM ); } else { - ArmBeam( -1 ); - ArmBeam( 1 ); - BeamGlow(); + Shotgun(); + + EMIT_SOUND( ENT( pev ), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM ); } - EMIT_SOUND_DYN( ENT( pev ), CHAN_WEAPON, "bigsmoke/fire1.wav", 1, ATTN_NORM, 0, 100 + m_iBeams * 10 ); - pev->skin = m_iBeams / 2; + CSoundEnt::InsertSound( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); } break; - case ISLAVE_AE_ZAP_SHOOT: + case HSMOKE_AE_BURST2: + case HSMOKE_AE_BURST3: + Shoot(); + break; + case HSMOKE_AE_KICK: { - ClearBeams(); + CBaseEntity *pHurt = Kick(); - if( m_hDead != NULL ) + if( pHurt ) { - Vector vecDest = m_hDead->pev->origin + Vector( 0, 0, 38 ); - TraceResult trace; - UTIL_TraceHull( vecDest, vecDest, dont_ignore_monsters, human_hull, m_hDead->edict(), &trace ); - - if( !trace.fStartSolid ) - { - CBaseEntity *pNew = Create( "monster_big_smoke", m_hDead->pev->origin, m_hDead->pev->angles ); - CBaseMonster *pNewMonster = pNew->MyMonsterPointer( ); - pNew->pev->spawnflags |= 1; - WackBeam( -1, pNew ); - WackBeam( 1, pNew ); - UTIL_Remove( m_hDead ); - /* - CBaseEntity *pEffect = Create( "test_effect", pNew->Center(), pev->angles ); - pEffect->Use( this, this, USE_ON, 1 ); - */ - break; - } + // 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 ); } - ClearMultiDamage(); - - UTIL_MakeAimVectors( pev->angles ); - - ZapBeam( -1 ); - ZapBeam( 1 ); - - // STOP_SOUND( ENT( pev ), CHAN_WEAPON, "debris/zap4.wav" ); - ApplyMultiDamage( pev, pev ); - - m_flNextAttack = gpGlobals->time + RANDOM_FLOAT( 0.5, 4.0 ); } break; - case ISLAVE_AE_ZAP_DONE: + case HSMOKE_AE_CAUGHT_ENEMY: { - ClearBeams(); + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "SM_ALERT", HSMOKE_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + } break; default: @@ -388,78 +879,10 @@ void CBigSmoke::HandleAnimEvent( MonsterEvent_t *pEvent ) } } -//========================================================= -// CheckRangeAttack1 - normal beam attack -//========================================================= -BOOL CBigSmoke::CheckRangeAttack1( float flDot, float flDist ) -{ - if( m_flNextAttack > gpGlobals->time ) - { - return FALSE; - } - - return CSquadMonster::CheckRangeAttack1( flDot, flDist ); -} - -//========================================================= -// CheckRangeAttack2 - check bravery and try to resurect dead comrades -//========================================================= -BOOL CBigSmoke::CheckRangeAttack2( float flDot, float flDist ) -{ - return FALSE; - - if( m_flNextAttack > gpGlobals->time ) - { - return FALSE; - } - - m_hDead = NULL; - m_iBravery = 0; - - CBaseEntity *pEntity = NULL; - while( ( pEntity = UTIL_FindEntityByClassname( pEntity, "monster_big_smoke" ) ) != NULL ) - { - TraceResult tr; - - UTIL_TraceLine( EyePosition(), pEntity->EyePosition(), ignore_monsters, ENT( pev ), &tr ); - if( tr.flFraction == 1.0 || tr.pHit == pEntity->edict() ) - { - if( pEntity->pev->deadflag == DEAD_DEAD ) - { - float d = ( pev->origin - pEntity->pev->origin ).Length(); - if( d < flDist ) - { - m_hDead = pEntity; - flDist = d; - } - m_iBravery--; - } - else - { - m_iBravery++; - } - } - } - if( m_hDead != NULL ) - return TRUE; - else - return FALSE; -} - -//========================================================= -// StartTask -//========================================================= -void CBigSmoke::StartTask( Task_t *pTask ) -{ - ClearBeams(); - - CSquadMonster::StartTask( pTask ); -} - //========================================================= // Spawn //========================================================= -void CBigSmoke::Spawn() +void CSmoke::Spawn() { Precache(); @@ -470,13 +893,55 @@ void CBigSmoke::Spawn() pev->movetype = MOVETYPE_STEP; m_bloodColor = BLOOD_COLOR_RED; pev->effects = 0; - pev->health = 200; - pev->view_ofs = Vector( 0, 0, 64 );// position of the eyes relative to monster's origin. - m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello + 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_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_RANGE_ATTACK2 | bits_CAP_DOORS_GROUP; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = HSMOKE_SENT_NONE; - m_voicePitch = RANDOM_LONG( 85, 110 ); + 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 = HSMOKE_9MMAR | HSMOKE_HANDGRENADE; + // pev->weapons = HSMOKE_SHOTGUN; + // pev->weapons = HSMOKE_9MMAR | HSMOKE_GRENADELAUNCHER; + } + + if( FBitSet( pev->weapons, HSMOKE_SHOTGUN ) ) + { + SetBodygroup( GUN_GROUP, GUN_SHOTGUN ); + m_cClipSize = 8; + } + else + { + m_cClipSize = GRUNT_CLIP_SIZE; + } + m_cAmmoLoaded = m_cClipSize; + + if( RANDOM_LONG( 0, 99 ) < 80 ) + pev->skin = 0; // light skin + else + pev->skin = 1; // dark skin + + if( FBitSet( pev->weapons, HSMOKE_SHOTGUN ) ) + { + SetBodygroup( HEAD_GROUP, HEAD_SHOTGUN ); + } + else if( FBitSet( pev->weapons, HSMOKE_GRENADELAUNCHER ) ) + { + SetBodygroup( HEAD_GROUP, HEAD_M203 ); + pev->skin = 1; // alway dark skin + } + + CTalkMonster::g_talkWaitTime = 0; MonsterInit(); } @@ -484,298 +949,1320 @@ void CBigSmoke::Spawn() //========================================================= // Precache - precaches all resources this monster needs //========================================================= -void CBigSmoke::Precache() +void CSmoke::Precache() { - int i; - PRECACHE_MODEL( "models/bigsmoke.mdl" ); - PRECACHE_MODEL( "sprites/xsmoke3down.spr" ); + PRECACHE_SOUND( "bigsmoke/fire1.wav" ); + + PRECACHE_SOUND( "hSmoke/gr_reload1.wav" ); - for( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) - PRECACHE_SOUND( (char *)pAttackHitSounds[i] ); + PRECACHE_SOUND( "weapons/glauncher.wav" ); - for( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) - PRECACHE_SOUND( (char *)pAttackMissSounds[i] ); + PRECACHE_SOUND( "weapons/sbarrel1.wav" ); - for( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) - PRECACHE_SOUND((char *)pPainSounds[i] ); + PRECACHE_SOUND( "zombie/claw_miss2.wav" );// because we use the basemonster SWIPE animation event - for( i = 0; i < ARRAYSIZE( pDeathSounds ); i++ ) - PRECACHE_SOUND( (char *)pDeathSounds[i] ); + // get voice pitch + m_voicePitch = 100; - UTIL_PrecacheOther( "test_effect" ); + m_iBrassShell = PRECACHE_MODEL( "models/shell.mdl" );// brass shell + m_iShotgunShell = PRECACHE_MODEL( "models/shotgunshell.mdl" ); } //========================================================= -// TakeDamage - get provoked when injured +// start task //========================================================= - -int CBigSmoke::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +void CSmoke::StartTask( Task_t *pTask ) { - // don't slash one of your own - if( ( bitsDamageType & DMG_BULLET ) && pevAttacker && IRelationship( Instance( pevAttacker ) ) < R_DL ) - return 0; + m_iTaskStatus = TASKSTATUS_RUNNING; - m_afMemory |= bits_MEMORY_PROVOKED; - return CSquadMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); + switch( pTask->iTask ) + { + case TASK_GRUNT_CHECK_FIRE: + if( !NoFriendlyFire() ) + { + SetConditions( bits_COND_GRUNT_NOFIRE ); + } + TaskComplete(); + break; + case TASK_GRUNT_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_GRUNT_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; + } } -void CBigSmoke::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +//========================================================= +// RunTask +//========================================================= +void CSmoke::RunTask( Task_t *pTask ) { - if( bitsDamageType & DMG_BULLET ) - return; + switch( pTask->iTask ) + { + case TASK_GRUNT_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 ); - CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + if( FacingIdeal() ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CSquadMonster::RunTask( pTask ); + break; + } + } } //========================================================= // AI Schedules Specific to this monster //========================================================= -// primary range attack -Task_t tlBigSmokeAttack1[] = +//========================================================= +// GruntFail +//========================================================= +Task_t tlSmokeFail[] = { - { TASK_STOP_MOVING, 0 }, - { TASK_FACE_IDEAL, (float)0 }, - { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, }; -Schedule_t slBigSmokeAttack1[] = +Schedule_t slSmokeFail[] = { - { - tlBigSmokeAttack1, - ARRAYSIZE ( tlBigSmokeAttack1 ), + { + tlSmokeFail, + ARRAYSIZE( tlSmokeFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK1 | - bits_COND_HEAR_SOUND | - bits_COND_HEAVY_DAMAGE, - - bits_SOUND_DANGER, - "Slave Range Attack1" + bits_COND_CAN_MELEE_ATTACK2, + 0, + "Grunt Fail" }, }; -DEFINE_CUSTOM_SCHEDULES( CBigSmoke ) +//========================================================= +// Grunt Combat Fail +//========================================================= +Task_t tlSmokeCombatFail[] = { - slBigSmokeAttack1, + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, }; -IMPLEMENT_CUSTOM_SCHEDULES( CBigSmoke, CSquadMonster ) +Schedule_t slSmokeCombatFail[] = +{ + { + tlSmokeCombatFail, + ARRAYSIZE( tlSmokeCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Grunt Combat Fail" + }, +}; //========================================================= +// Victory dance! //========================================================= -Schedule_t *CBigSmoke::GetSchedule( void ) +Task_t tlSmokeVictoryDance[] = +{ + { 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 slSmokeVictoryDance[] = { - ClearBeams(); -/* - if( pev->spawnflags ) { - pev->spawnflags = 0; - return GetScheduleOfType( SCHED_RELOAD ); + tlSmokeVictoryDance, + ARRAYSIZE( tlSmokeVictoryDance ), + 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 tlSmokeEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slSmokeEstablishLineOfFire[] = +{ + { + tlSmokeEstablishLineOfFire, + ARRAYSIZE( tlSmokeEstablishLineOfFire ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntEstablishLineOfFire" + }, +}; + +//========================================================= +// GruntFoundEnemy - grunt established sight with an enemy +// that was hiding from the squad. +//========================================================= +Task_t tlSmokeFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL1 }, +}; + +Schedule_t slSmokeFoundEnemy[] = +{ + { + tlSmokeFoundEnemy, + ARRAYSIZE( tlSmokeFoundEnemy ), + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntFoundEnemy" + }, +}; + +//========================================================= +// GruntCombatFace Schedule +//========================================================= +Task_t tlSmokeCombatFace1[] = +{ + { 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_GRUNT_SWEEP }, +}; + +Schedule_t slSmokeCombatFace[] = +{ + { + tlSmokeCombatFace1, + ARRAYSIZE( tlSmokeCombatFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Suppressing fire - don't stop shooting until the clip is +// empty or grunt gets hurt. +//========================================================= +Task_t tlSmokeSignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0}, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slSmokeSignalSuppress[] = +{ + { + tlSmokeSignalSuppress, + ARRAYSIZE( tlSmokeSignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; + +Task_t tlSmokeSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slSmokeSuppress[] = +{ + { + tlSmokeSuppress, + ARRAYSIZE( tlSmokeSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_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 tlSmokeWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slSmokeWaitInCover[] = +{ + { + tlSmokeWaitInCover, + ARRAYSIZE( tlSmokeWaitInCover ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + bits_SOUND_DANGER, + "GruntWaitInCover" + }, +}; + +//========================================================= +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. +//========================================================= +Task_t tlSmokeTakeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_GRUNT_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_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slSmokeTakeCover[] = +{ + { + tlSmokeTakeCover1, + ARRAYSIZE ( tlSmokeTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlSmokeGrenadeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)99 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_PLAY_SEQUENCE, (float)ACT_SPECIAL_ATTACK1 }, + { TASK_CLEAR_MOVE_WAIT, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slSmokeGrenadeCover[] = +{ + { + tlSmokeGrenadeCover1, + ARRAYSIZE( tlSmokeGrenadeCover1 ), + 0, + 0, + "GrenadeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlSmokeTossGrenadeCover1[] = +{ + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slSmokeTossGrenadeCover[] = +{ + { + tlSmokeTossGrenadeCover1, + ARRAYSIZE( tlSmokeTossGrenadeCover1 ), + 0, + 0, + "TossGrenadeCover" + }, +}; + +//========================================================= +// hide from the loudest sound source (to run from grenade) +//========================================================= +Task_t tlSmokeTakeCoverFromBestSound[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_COWER },// duck and cover if cannot move from explosion + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slSmokeTakeCoverFromBestSound[] = +{ + { + tlSmokeTakeCoverFromBestSound, + ARRAYSIZE( tlSmokeTakeCoverFromBestSound ), + 0, + 0, + "GruntTakeCoverFromBestSound" + }, +}; + +//========================================================= +// Grunt reload schedule +//========================================================= +Task_t tlSmokeHideReload[] = +{ + { 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 slSmokeHideReload[] = +{ + { + tlSmokeHideReload, + ARRAYSIZE( tlSmokeHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "GruntHideReload" } -*/ +}; + +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlSmokeSweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slSmokeSweep[] = +{ + { + tlSmokeSweep, + ARRAYSIZE( tlSmokeSweep ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_HEAR_SOUND, + bits_SOUND_WORLD |// sound flags + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + "Grunt 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 tlSmokeRangeAttack1A[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slSmokeRangeAttack1A[] = +{ + { + tlSmokeRangeAttack1A, + ARRAYSIZE( tlSmokeRangeAttack1A ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_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 tlSmokeRangeAttack1B[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_IDLE_ANGRY }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slSmokeRangeAttack1B[] = +{ + { + tlSmokeRangeAttack1B, + ARRAYSIZE( tlSmokeRangeAttack1B ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_GRUNT_NOFIRE | + bits_COND_HEAR_SOUND, + bits_SOUND_DANGER, + "Range Attack1B" + }, +}; + +//========================================================= +// secondary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlSmokeRangeAttack2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GRUNT_FACE_TOSS_DIR, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RANGE_ATTACK2 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY },// don't run immediately after throwing grenade. +}; + +Schedule_t slSmokeRangeAttack2[] = +{ + { + tlSmokeRangeAttack2, + ARRAYSIZE( tlSmokeRangeAttack2 ), + 0, + 0, + "RangeAttack2" + }, +}; + +//========================================================= +// repel +//========================================================= +Task_t tlSmokeRepel[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_GLIDE }, +}; + +Schedule_t slSmokeRepel[] = +{ + { + tlSmokeRepel, + ARRAYSIZE( tlSmokeRepel ), + 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 tlSmokeRepelAttack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_FLY }, +}; + +Schedule_t slSmokeRepelAttack[] = +{ + { + tlSmokeRepelAttack, + ARRAYSIZE( tlSmokeRepelAttack ), + bits_COND_ENEMY_OCCLUDED, + 0, + "Repel Attack" + }, +}; + +//========================================================= +// repel land +//========================================================= +Task_t tlSmokeRepelLand[] = +{ + { 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 slSmokeRepelLand[] = +{ + { + tlSmokeRepelLand, + ARRAYSIZE( tlSmokeRepelLand ), + 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( CSmoke ) +{ + slSmokeFail, + slSmokeCombatFail, + slSmokeVictoryDance, + slSmokeEstablishLineOfFire, + slSmokeFoundEnemy, + slSmokeCombatFace, + slSmokeSignalSuppress, + slSmokeSuppress, + slSmokeWaitInCover, + slSmokeTakeCover, + slSmokeGrenadeCover, + slSmokeTossGrenadeCover, + slSmokeTakeCoverFromBestSound, + slSmokeHideReload, + slSmokeSweep, + slSmokeRangeAttack1A, + slSmokeRangeAttack1B, + slSmokeRangeAttack2, + slSmokeRepel, + slSmokeRepelAttack, + slSmokeRepelLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CSmoke, CSquadMonster ) + +//========================================================= +// SetActivity +//========================================================= +void CSmoke::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, HSMOKE_9MMAR ) ) + { + if( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_mp5" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_mp5" ); + } + } + else + { + if( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_shotgun" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_shotgun" ); + } + } + break; + case ACT_RANGE_ATTACK2: + // grunt is going to a secondary long range attack. This may be a thrown + // grenade or fired grenade, we must determine which and pick proper sequence + if( pev->weapons & HSMOKE_HANDGRENADE ) + { + // get toss anim + iSequence = LookupSequence( "throwgrenade" ); + } + else + { + // get launch anim + iSequence = LookupSequence( "launchgrenade" ); + } + break; + case ACT_RUN: + if( pev->health <= HSMOKE_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity( ACT_RUN_HURT ); + } + else + { + iSequence = LookupActivity( NewActivity ); + } + break; + case ACT_WALK: + if( pev->health <= HSMOKE_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 *CSmoke::GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = HSMOKE_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_GRUNT_REPEL_LAND ); + } + else + { + // repel down a rope, + if( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType( SCHED_GRUNT_REPEL_ATTACK ); + else + return GetScheduleOfType( SCHED_GRUNT_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! - if( pSound && ( pSound->m_iType & bits_SOUND_DANGER ) ) - return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); - if( pSound->m_iType & bits_SOUND_COMBAT ) - m_afMemory |= bits_MEMORY_PROVOKED; + //!!!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 ), "SM_GREN", HSMOKE_SENTENCE_VOLUME, GRUNT_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(); - } - - if( pev->health < 20 || m_iBravery < 0 ) - { - if( !HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + // dead enemy + if( HasConditions( bits_COND_ENEMY_DEAD ) ) { - m_failSchedule = SCHED_CHASE_ENEMY; - if( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + // 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 ), "SM_ALERT", HSMOKE_SENTENCE_VOLUME, GRUNT_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 ), "SM_MONST", HSMOKE_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + + JustSpoke(); + } + + if( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_GRUNT_SUPPRESS ); + } + else + { + return GetScheduleOfType( SCHED_GRUNT_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_GRUNT_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 ), "SM_COVER", HSMOKE_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + m_iSentence = HSMOKE_SENT_COVER; + //JustSpoke(); + } return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); } - if( HasConditions( bits_COND_SEE_ENEMY ) && HasConditions( bits_COND_ENEMY_FACING_ME ) ) + else { - // ALERT( at_console, "exposed\n"); + 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, HSMOKE_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_GRUNT_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 ), "SM_THROW", HSMOKE_SENTENCE_VOLUME, GRUNT_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 ), "SM_CHARGE", HSMOKE_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + m_iSentence = HSMOKE_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( SCHED_GRUNT_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 ), "SM_TAUNT", HSMOKE_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } } break; default: break; } + + // no special cases here, call the base class return CSquadMonster::GetSchedule(); } -Schedule_t *CBigSmoke::GetScheduleOfType( int Type ) +//========================================================= +//========================================================= +Schedule_t *CSmoke::GetScheduleOfType( int Type ) { switch( Type ) { - case SCHED_FAIL: - if( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + case SCHED_TAKE_COVER_FROM_ENEMY: { - return CSquadMonster::GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + if( InSquad() ) + { + if( g_iSkillLevel == SKILL_HARD && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + if( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz( ENT( pev ), "SM_THROW", HSMOKE_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch ); + JustSpoke(); + } + return slSmokeTossGrenadeCover; + } + else + { + return &slSmokeTakeCover[0]; + } + } + else + { + if( RANDOM_LONG( 0, 1 ) ) + { + return &slSmokeTakeCover[0]; + } + else + { + return &slSmokeGrenadeCover[0]; + } + } + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slSmokeTakeCoverFromBestSound[0]; + } + case SCHED_GRUNT_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_GRUNT_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_GRUNT_ESTABLISH_LINE_OF_FIRE: + { + return &slSmokeEstablishLineOfFire[0]; } break; case SCHED_RANGE_ATTACK1: - return slBigSmokeAttack1; + { + // randomly stand or crouch + if( RANDOM_LONG( 0, 9 ) == 0 ) + m_fStanding = RANDOM_LONG( 0, 1 ); + + if( m_fStanding ) + return &slSmokeRangeAttack1B[0]; + else + return &slSmokeRangeAttack1A[0]; + } case SCHED_RANGE_ATTACK2: - return slBigSmokeAttack1; - } - return CSquadMonster::GetScheduleOfType( Type ); -} - -//========================================================= -// ArmBeam - small beam from arm to nearby geometry -//========================================================= -void CBigSmoke::ArmBeam( int side ) -{ - TraceResult tr; - float flDist = 1.0; - - if( m_iBeams >= ISLAVE_MAX_BEAMS ) - return; - - UTIL_MakeAimVectors( pev->angles ); - Vector vecSrc = pev->origin + gpGlobals->v_up * 36 + gpGlobals->v_right * side * 16 + gpGlobals->v_forward * 32; - - for( int i = 0; i < 3; i++ ) - { - Vector vecAim = gpGlobals->v_right * side * RANDOM_FLOAT( 0, 1 ) + gpGlobals->v_up * RANDOM_FLOAT( -1, 1 ); - TraceResult tr1; - UTIL_TraceLine( vecSrc, vecSrc + vecAim * 512, dont_ignore_monsters, ENT( pev ), &tr1 ); - if( flDist > tr1.flFraction ) { - tr = tr1; - flDist = tr.flFraction; + return &slSmokeRangeAttack2[0]; } - } - - // Couldn't find anything close enough - if( flDist == 1.0 ) - return; - - DecalGunshot( &tr, BULLET_PLAYER_CROWBAR ); - - m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/xsmoke3down.spr", 30 ); - if( !m_pBeam[m_iBeams] ) - return; - - m_pBeam[m_iBeams]->PointEntInit( tr.vecEndPos, entindex() ); - m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); - // m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); - m_pBeam[m_iBeams]->SetColor( 6, 0, 106 ); - m_pBeam[m_iBeams]->SetBrightness( 0 ); - m_pBeam[m_iBeams]->SetNoise( 80 ); - m_iBeams++; -} - -//========================================================= -// BeamGlow - brighten all beams -//========================================================= -void CBigSmoke::BeamGlow() -{ - int b = m_iBeams * 32; - if( b > 255 ) - b = 255; - - for( int i = 0; i < m_iBeams; i++ ) - { - if( m_pBeam[i]->GetBrightness() != 255 ) + case SCHED_COMBAT_FACE: { - m_pBeam[i]->SetBrightness( b ); + return &slSmokeCombatFace[0]; + } + case SCHED_GRUNT_WAIT_FACE_ENEMY: + { + return &slSmokeWaitInCover[0]; + } + case SCHED_GRUNT_SWEEP: + { + return &slSmokeSweep[0]; + } + case SCHED_GRUNT_COVER_AND_RELOAD: + { + return &slSmokeHideReload[0]; + } + case SCHED_GRUNT_FOUND_ENEMY: + { + return &slSmokeFoundEnemy[0]; + } + case SCHED_VICTORY_DANCE: + { + if( InSquad() ) + { + if( !IsLeader() ) + { + return &slSmokeFail[0]; + } + } + + return &slSmokeVictoryDance[0]; + } + case SCHED_GRUNT_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 &slSmokeSignalSuppress[0]; + } + else + { + return &slSmokeSuppress[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 &slSmokeCombatFail[0]; + } + + return &slSmokeFail[0]; + } + case SCHED_GRUNT_REPEL: + { + if( pev->velocity.z > -128 ) + pev->velocity.z -= 32; + return &slSmokeRepel[0]; + } + case SCHED_GRUNT_REPEL_ATTACK: + { + if( pev->velocity.z > -128 ) + pev->velocity.z -= 32; + return &slSmokeRepelAttack[0]; + } + case SCHED_GRUNT_REPEL_LAND: + { + return &slSmokeRepelLand[0]; + } + default: + { + return CSquadMonster::GetScheduleOfType( Type ); } } } //========================================================= -// WackBeam - regenerate dead colleagues +// DEAD HSMOKE PROP //========================================================= -void CBigSmoke::WackBeam( int side, CBaseEntity *pEntity ) +class CDeadSmoke : public CBaseMonster { - Vector vecDest; - float flDist = 1.0; +public: + void Spawn( void ); + int Classify( void ) { return CLASS_HUMAN_MILITARY; } - if( m_iBeams >= ISLAVE_MAX_BEAMS ) - return; + void KeyValue( KeyValueData *pkvd ); - if( pEntity == NULL ) - return; + int m_iPose;// which sequence to display -- temporary, don't need to save + static const char *m_szPoses[3]; +}; - m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/xsmoke3down.spr", 30 ); - if( !m_pBeam[m_iBeams] ) - return; +const char *CDeadSmoke::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; - m_pBeam[m_iBeams]->PointEntInit( pEntity->Center(), entindex() ); - m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); - m_pBeam[m_iBeams]->SetColor( 0, 5, 196 ); - m_pBeam[m_iBeams]->SetBrightness( 0 ); - m_pBeam[m_iBeams]->SetNoise( 80 ); - m_iBeams++; -} - -//========================================================= -// ZapBeam - heavy damage directly forward -//========================================================= -void CBigSmoke::ZapBeam( int side ) +void CDeadSmoke::KeyValue( KeyValueData *pkvd ) { - Vector vecSrc, vecAim; - TraceResult tr; - CBaseEntity *pEntity; - - if( m_iBeams >= ISLAVE_MAX_BEAMS ) - return; - - vecSrc = pev->origin + gpGlobals->v_up * 36; - vecAim = ShootAtEnemy( vecSrc ); - float deflection = 0.01; - vecAim = vecAim + side * gpGlobals->v_right * RANDOM_FLOAT( 0, deflection ) + gpGlobals->v_up * RANDOM_FLOAT( -deflection, deflection ); - UTIL_TraceLine( vecSrc, vecSrc + vecAim * 1024, dont_ignore_monsters, ENT( pev ), &tr ); - - m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/xsmoke3down.spr", 50 ); - if( !m_pBeam[m_iBeams] ) - return; - - m_pBeam[m_iBeams]->PointEntInit( tr.vecEndPos, entindex() ); - m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); - m_pBeam[m_iBeams]->SetColor( 0, 0, 206 ); - m_pBeam[m_iBeams]->SetBrightness( 0 ); - m_pBeam[m_iBeams]->SetNoise( 20 ); - m_iBeams++; - - pEntity = CBaseEntity::Instance( tr.pHit ); - if( pEntity != NULL && pEntity->pev->takedamage ) + if( FStrEq( pkvd->szKeyName, "pose" ) ) { - pEntity->TraceAttack( pev, 1, vecAim, &tr, DMG_BULLET ); + m_iPose = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; } + else + CBaseMonster::KeyValue( pkvd ); } +LINK_ENTITY_TO_CLASS( monster_smoke_dead, CDeadSmoke ) + //========================================================= -// ClearBeams - remove all beams +// ********** DeadHSmoke SPAWN ********** //========================================================= -void CBigSmoke::ClearBeams() +void CDeadSmoke::Spawn( void ) { - for( int i = 0; i < ISLAVE_MAX_BEAMS; i++ ) + PRECACHE_MODEL( "models/bigsmoke.mdl" ); + SET_MODEL( ENT( pev ), "models/bigsmoke.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 ) { - if( m_pBeam[i] ) - { - UTIL_Remove( m_pBeam[i] ); - m_pBeam[i] = NULL; - } + ALERT( at_console, "Dead hSmoke with bad pose\n" ); } - m_iBeams = 0; - pev->skin = 0; + + // 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_GROUP, HEAD_GRUNT ); + 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: + // Grunt no Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_GRUNT ); + 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(); }