2008-09-15 01:07:45 -05:00
//========= Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: This is the soldier version of the combine, analogous to the HL1 grunt.
//
//=============================================================================//
# include "cbase.h"
# include "ai_hull.h"
# include "ai_motor.h"
# include "npc_combines.h"
# include "bitstring.h"
# include "engine/IEngineSound.h"
# include "soundent.h"
# include "ndebugoverlay.h"
# include "npcevent.h"
# include "hl2/hl2_player.h"
# include "game.h"
# include "ammodef.h"
# include "explode.h"
# include "ai_memory.h"
# include "Sprite.h"
# include "soundenvelope.h"
# include "weapon_physcannon.h"
# include "hl2_gamerules.h"
# include "gameweaponmanager.h"
# include "vehicle_base.h"
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
ConVar sk_combine_s_health ( " sk_combine_s_health " , " 0 " ) ;
ConVar sk_combine_s_kick ( " sk_combine_s_kick " , " 0 " ) ;
ConVar sk_combine_guard_health ( " sk_combine_guard_health " , " 0 " ) ;
ConVar sk_combine_guard_kick ( " sk_combine_guard_kick " , " 0 " ) ;
// Whether or not the combine guard should spawn health on death
ConVar combine_guard_spawn_health ( " combine_guard_spawn_health " , " 1 " ) ;
extern ConVar sk_plr_dmg_buckshot ;
extern ConVar sk_plr_num_shotgun_pellets ;
//Whether or not the combine should spawn health on death
ConVar combine_spawn_health ( " combine_spawn_health " , " 1 " ) ;
LINK_ENTITY_TO_CLASS ( npc_combine_s , CNPC_CombineS ) ;
# define AE_SOLDIER_BLOCK_PHYSICS 20 // trying to block an incoming physics object
extern Activity ACT_WALK_EASY ;
extern Activity ACT_WALK_MARCH ;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CNPC_CombineS : : Spawn ( void )
{
Precache ( ) ;
SetModel ( STRING ( GetModelName ( ) ) ) ;
if ( IsElite ( ) )
{
// Stronger, tougher.
2008-09-15 02:50:57 -05:00
SetHealth ( sk_combine_guard_health . GetInt ( ) ) ;
SetMaxHealth ( sk_combine_guard_health . GetInt ( ) ) ;
SetKickDamage ( sk_combine_guard_kick . GetInt ( ) ) ;
2008-09-15 01:07:45 -05:00
}
else
{
2008-09-15 02:50:57 -05:00
SetHealth ( sk_combine_s_health . GetInt ( ) ) ;
SetMaxHealth ( sk_combine_s_health . GetInt ( ) ) ;
SetKickDamage ( sk_combine_s_kick . GetInt ( ) ) ;
2008-09-15 01:07:45 -05:00
}
CapabilitiesAdd ( bits_CAP_ANIMATEDFACE ) ;
CapabilitiesAdd ( bits_CAP_MOVE_SHOOT ) ;
CapabilitiesAdd ( bits_CAP_DOORS_GROUP ) ;
BaseClass : : Spawn ( ) ;
# if HL2_EPISODIC
if ( m_iUseMarch & & ! HasSpawnFlags ( SF_NPC_START_EFFICIENT ) )
{
Msg ( " Soldier %s is set to use march anim, but is not an efficient AI. The blended march anim can only be used for dead-ahead walks! \n " , GetDebugName ( ) ) ;
}
# endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CNPC_CombineS : : Precache ( )
{
const char * pModelName = STRING ( GetModelName ( ) ) ;
if ( ! Q_stricmp ( pModelName , " models/combine_super_soldier.mdl " ) )
{
m_fIsElite = true ;
}
else
{
m_fIsElite = false ;
}
if ( ! GetModelName ( ) )
{
SetModelName ( MAKE_STRING ( " models/combine_soldier.mdl " ) ) ;
}
PrecacheModel ( STRING ( GetModelName ( ) ) ) ;
UTIL_PrecacheOther ( " item_healthvial " ) ;
UTIL_PrecacheOther ( " weapon_frag " ) ;
UTIL_PrecacheOther ( " item_ammo_ar2_altfire " ) ;
BaseClass : : Precache ( ) ;
}
void CNPC_CombineS : : DeathSound ( const CTakeDamageInfo & info )
{
// NOTE: The response system deals with this at the moment
if ( GetFlags ( ) & FL_DISSOLVING )
return ;
GetSentences ( ) - > Speak ( " COMBINE_DIE " , SENTENCE_PRIORITY_INVALID , SENTENCE_CRITERIA_ALWAYS ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Soldiers use CAN_RANGE_ATTACK2 to indicate whether they can throw
// a grenade. Because they check only every half-second or so, this
// condition must persist until it is updated again by the code
// that determines whether a grenade can be thrown, so prevent the
// base class from clearing it out. (sjb)
//-----------------------------------------------------------------------------
void CNPC_CombineS : : ClearAttackConditions ( )
{
bool fCanRangeAttack2 = HasCondition ( COND_CAN_RANGE_ATTACK2 ) ;
// Call the base class.
BaseClass : : ClearAttackConditions ( ) ;
if ( fCanRangeAttack2 )
{
// We don't allow the base class to clear this condition because we
// don't sense for it every frame.
SetCondition ( COND_CAN_RANGE_ATTACK2 ) ;
}
}
void CNPC_CombineS : : PrescheduleThink ( void )
{
/*//FIXME: This doesn't need to be in here, it's all debug info
if ( HasCondition ( COND_HEAR_PHYSICS_DANGER ) )
{
// Don't react unless we see the item!!
CSound * pSound = NULL ;
pSound = GetLoudestSoundOfType ( SOUND_PHYSICS_DANGER ) ;
if ( pSound )
{
if ( FInViewCone ( pSound - > GetSoundReactOrigin ( ) ) )
{
DevMsg ( " OH CRAP! \n " ) ;
NDebugOverlay : : Line ( EyePosition ( ) , pSound - > GetSoundReactOrigin ( ) , 0 , 0 , 255 , false , 2.0f ) ;
}
}
}
*/
BaseClass : : PrescheduleThink ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Allows for modification of the interrupt mask for the current schedule.
// In the most cases the base implementation should be called first.
//-----------------------------------------------------------------------------
void CNPC_CombineS : : BuildScheduleTestBits ( void )
{
//Interrupt any schedule with physics danger (as long as I'm not moving or already trying to block)
if ( m_flGroundSpeed = = 0.0 & & ! IsCurSchedule ( SCHED_FLINCH_PHYSICS ) )
{
SetCustomInterruptCondition ( COND_HEAR_PHYSICS_DANGER ) ;
}
BaseClass : : BuildScheduleTestBits ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
int CNPC_CombineS : : SelectSchedule ( void )
{
return BaseClass : : SelectSchedule ( ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
float CNPC_CombineS : : GetHitgroupDamageMultiplier ( int iHitGroup , const CTakeDamageInfo & info )
{
switch ( iHitGroup )
{
case HITGROUP_HEAD :
{
// Soldiers take double headshot damage
return 2.0f ;
}
}
return BaseClass : : GetHitgroupDamageMultiplier ( iHitGroup , info ) ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CNPC_CombineS : : HandleAnimEvent ( animevent_t * pEvent )
{
switch ( pEvent - > event )
{
case AE_SOLDIER_BLOCK_PHYSICS :
DevMsg ( " BLOCKING! \n " ) ;
m_fIsBlocking = true ;
break ;
default :
BaseClass : : HandleAnimEvent ( pEvent ) ;
break ;
}
}
void CNPC_CombineS : : OnChangeActivity ( Activity eNewActivity )
{
// Any new sequence stops us blocking.
m_fIsBlocking = false ;
BaseClass : : OnChangeActivity ( eNewActivity ) ;
# if HL2_EPISODIC
// Give each trooper a varied look for his march. Done here because if you do it earlier (eg Spawn, StartTask), the
// pose param gets overwritten.
if ( m_iUseMarch )
{
SetPoseParameter ( " casual " , RandomFloat ( ) ) ;
}
# endif
}
void CNPC_CombineS : : OnListened ( )
{
BaseClass : : OnListened ( ) ;
if ( HasCondition ( COND_HEAR_DANGER ) & & HasCondition ( COND_HEAR_PHYSICS_DANGER ) )
{
if ( HasInterruptCondition ( COND_HEAR_DANGER ) )
{
ClearCondition ( COND_HEAR_PHYSICS_DANGER ) ;
}
}
// debugging to find missed schedules
#if 0
if ( HasCondition ( COND_HEAR_DANGER ) & & ! HasInterruptCondition ( COND_HEAR_DANGER ) )
{
DevMsg ( " Ignore danger in %s \n " , GetCurSchedule ( ) - > GetName ( ) ) ;
}
# endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &info -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
void CNPC_CombineS : : Event_Killed ( const CTakeDamageInfo & info )
{
// Don't bother if we've been told not to, or the player has a megaphyscannon
if ( combine_spawn_health . GetBool ( ) = = false | | PlayerHasMegaPhysCannon ( ) )
{
BaseClass : : Event_Killed ( info ) ;
return ;
}
CBasePlayer * pPlayer = ToBasePlayer ( info . GetAttacker ( ) ) ;
if ( ! pPlayer )
{
CPropVehicleDriveable * pVehicle = dynamic_cast < CPropVehicleDriveable * > ( info . GetAttacker ( ) ) ;
if ( pVehicle & & pVehicle - > GetDriver ( ) & & pVehicle - > GetDriver ( ) - > IsPlayer ( ) )
{
pPlayer = assert_cast < CBasePlayer * > ( pVehicle - > GetDriver ( ) ) ;
}
}
if ( pPlayer ! = NULL )
{
// Elites drop alt-fire ammo, so long as they weren't killed by dissolving.
if ( IsElite ( ) )
{
# ifdef HL2_EPISODIC
if ( HasSpawnFlags ( SF_COMBINE_NO_AR2DROP ) = = false )
# endif
{
CBaseEntity * pItem = DropItem ( " item_ammo_ar2_altfire " , WorldSpaceCenter ( ) + RandomVector ( - 4 , 4 ) , RandomAngle ( 0 , 360 ) ) ;
if ( pItem )
{
IPhysicsObject * pObj = pItem - > VPhysicsGetObject ( ) ;
if ( pObj )
{
Vector vel = RandomVector ( - 64.0f , 64.0f ) ;
AngularImpulse angImp = RandomAngularImpulse ( - 300.0f , 300.0f ) ;
vel [ 2 ] = 0.0f ;
pObj - > AddVelocity ( & vel , & angImp ) ;
}
if ( info . GetDamageType ( ) & DMG_DISSOLVE )
{
CBaseAnimating * pAnimating = dynamic_cast < CBaseAnimating * > ( pItem ) ;
if ( pAnimating )
{
pAnimating - > Dissolve ( NULL , gpGlobals - > curtime , false , ENTITY_DISSOLVE_NORMAL ) ;
}
}
else
{
WeaponManager_AddManaged ( pItem ) ;
}
}
}
}
CHalfLife2 * pHL2GameRules = static_cast < CHalfLife2 * > ( g_pGameRules ) ;
// Attempt to drop health
if ( pHL2GameRules - > NPC_ShouldDropHealth ( pPlayer ) )
{
DropItem ( " item_healthvial " , WorldSpaceCenter ( ) + RandomVector ( - 4 , 4 ) , RandomAngle ( 0 , 360 ) ) ;
pHL2GameRules - > NPC_DroppedHealth ( ) ;
}
if ( HasSpawnFlags ( SF_COMBINE_NO_GRENADEDROP ) = = false )
{
// Attempt to drop a grenade
if ( pHL2GameRules - > NPC_ShouldDropGrenade ( pPlayer ) )
{
DropItem ( " weapon_frag " , WorldSpaceCenter ( ) + RandomVector ( - 4 , 4 ) , RandomAngle ( 0 , 360 ) ) ;
pHL2GameRules - > NPC_DroppedGrenade ( ) ;
}
}
}
BaseClass : : Event_Killed ( info ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &info -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_CombineS : : IsLightDamage ( const CTakeDamageInfo & info )
{
return BaseClass : : IsLightDamage ( info ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &info -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CNPC_CombineS : : IsHeavyDamage ( const CTakeDamageInfo & info )
{
// Combine considers AR2 fire to be heavy damage
if ( info . GetAmmoType ( ) = = GetAmmoDef ( ) - > Index ( " AR2 " ) )
return true ;
// 357 rounds are heavy damage
if ( info . GetAmmoType ( ) = = GetAmmoDef ( ) - > Index ( " 357 " ) )
return true ;
// Shotgun blasts where at least half the pellets hit me are heavy damage
if ( info . GetDamageType ( ) & DMG_BUCKSHOT )
{
2008-09-15 02:50:57 -05:00
int iHalfMax = ( int ) ( sk_plr_dmg_buckshot . GetFloat ( ) * sk_plr_num_shotgun_pellets . GetInt ( ) * 0.5 ) ;
2008-09-15 01:07:45 -05:00
if ( info . GetDamage ( ) > = iHalfMax )
return true ;
}
// Rollermine shocks
if ( ( info . GetDamageType ( ) & DMG_SHOCK ) & & hl2_episodic . GetBool ( ) )
{
return true ;
}
return BaseClass : : IsHeavyDamage ( info ) ;
}
# if HL2_EPISODIC
//-----------------------------------------------------------------------------
// Purpose: Translate base class activities into combot activites
//-----------------------------------------------------------------------------
Activity CNPC_CombineS : : NPC_TranslateActivity ( Activity eNewActivity )
{
// If the special ep2_outland_05 "use march" flag is set, use the more casual marching anim.
if ( m_iUseMarch & & eNewActivity = = ACT_WALK )
{
eNewActivity = ACT_WALK_MARCH ;
}
return BaseClass : : NPC_TranslateActivity ( eNewActivity ) ;
}
//---------------------------------------------------------
// Save/Restore
//---------------------------------------------------------
BEGIN_DATADESC ( CNPC_CombineS )
DEFINE_KEYFIELD ( m_iUseMarch , FIELD_INTEGER , " usemarch " ) ,
END_DATADESC ( )
2008-09-15 02:50:57 -05:00
# endif