//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Base combat character with no AI
//
//=============================================================================//
# include "cbase.h"
# include "basecombatcharacter.h"
# include "basecombatweapon.h"
# include "animation.h"
# include "gib.h"
# include "entitylist.h"
# include "gamerules.h"
# include "ai_basenpc.h"
# include "ai_squadslot.h"
# include "ammodef.h"
# include "ndebugoverlay.h"
# include "player.h"
# include "physics.h"
# include "engine/IEngineSound.h"
# include "tier1/strtools.h"
# include "sendproxy.h"
# include "EntityFlame.h"
# include "CRagdollMagnet.h"
# include "IEffects.h"
# include "iservervehicle.h"
# include "igamesystem.h"
# include "globals.h"
# include "physics_prop_ragdoll.h"
# include "physics_impact_damage.h"
# include "saverestore_utlvector.h"
# include "eventqueue.h"
# include "world.h"
# include "globalstate.h"
# include "items.h"
# include "movevars_shared.h"
# include "RagdollBoogie.h"
# include "rumble_shared.h"
# include "saverestoretypes.h"
# include "nav_mesh.h"
# ifdef NEXT_BOT
# include "NextBot/NextBotManager.h"
# endif
# ifdef HL2_DLL
# include "weapon_physcannon.h"
# include "hl2_gamerules.h"
# endif
# ifdef PORTAL
# include "portal_util_shared.h"
# include "prop_portal_shared.h"
# include "portal_shareddefs.h"
# endif
// memdbgon must be the last include file in a .cpp file!!!
# include "tier0/memdbgon.h"
# ifdef HL2_DLL
extern int g_interactionBarnacleVictimReleased ;
# endif //HL2_DLL
extern ConVar weapon_showproficiency ;
ConVar ai_show_hull_attacks ( " ai_show_hull_attacks " , " 0 " ) ;
ConVar ai_force_serverside_ragdoll ( " ai_force_serverside_ragdoll " , " 0 " ) ;
ConVar nb_last_area_update_tolerance ( " nb_last_area_update_tolerance " , " 4.0 " , FCVAR_CHEAT , " Distance a character needs to travel in order to invalidate cached area " ) ; // 4.0 tested as sweet spot (for wanderers, at least). More resulted in little benefit, less quickly diminished benefit [7/31/2008 tom]
# ifndef _RETAIL
ConVar ai_use_visibility_cache ( " ai_use_visibility_cache " , " 1 " ) ;
# define ShouldUseVisibilityCache() ai_use_visibility_cache.GetBool()
# else
# define ShouldUseVisibilityCache() true
# endif
BEGIN_DATADESC ( CBaseCombatCharacter )
# ifdef INVASION_DLL
DEFINE_FIELD ( m_iPowerups , FIELD_INTEGER ) ,
DEFINE_ARRAY ( m_flPowerupAttemptTimes , FIELD_TIME , MAX_POWERUPS ) ,
DEFINE_ARRAY ( m_flPowerupEndTimes , FIELD_TIME , MAX_POWERUPS ) ,
DEFINE_FIELD ( m_flFractionalBoost , FIELD_FLOAT ) ,
# endif
DEFINE_FIELD ( m_flNextAttack , FIELD_TIME ) ,
DEFINE_FIELD ( m_eHull , FIELD_INTEGER ) ,
DEFINE_FIELD ( m_bloodColor , FIELD_INTEGER ) ,
DEFINE_FIELD ( m_iDamageCount , FIELD_INTEGER ) ,
DEFINE_FIELD ( m_flFieldOfView , FIELD_FLOAT ) ,
DEFINE_FIELD ( m_HackedGunPos , FIELD_VECTOR ) ,
DEFINE_KEYFIELD ( m_RelationshipString , FIELD_STRING , " Relationship " ) ,
DEFINE_FIELD ( m_LastHitGroup , FIELD_INTEGER ) ,
DEFINE_FIELD ( m_flDamageAccumulator , FIELD_FLOAT ) ,
DEFINE_INPUT ( m_impactEnergyScale , FIELD_FLOAT , " physdamagescale " ) ,
DEFINE_FIELD ( m_CurrentWeaponProficiency , FIELD_INTEGER ) ,
DEFINE_UTLVECTOR ( m_Relationship , FIELD_EMBEDDED ) ,
DEFINE_AUTO_ARRAY ( m_iAmmo , FIELD_INTEGER ) ,
DEFINE_AUTO_ARRAY ( m_hMyWeapons , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_hActiveWeapon , FIELD_EHANDLE ) ,
DEFINE_FIELD ( m_bForceServerRagdoll , FIELD_BOOLEAN ) ,
DEFINE_FIELD ( m_bPreventWeaponPickup , FIELD_BOOLEAN ) ,
DEFINE_INPUTFUNC ( FIELD_VOID , " KilledNPC " , InputKilledNPC ) ,
END_DATADESC ( )
BEGIN_SIMPLE_DATADESC ( Relationship_t )
DEFINE_FIELD ( entity , FIELD_EHANDLE ) ,
DEFINE_FIELD ( classType , FIELD_INTEGER ) ,
DEFINE_FIELD ( disposition , FIELD_INTEGER ) ,
DEFINE_FIELD ( priority , FIELD_INTEGER ) ,
END_DATADESC ( )
//-----------------------------------------------------------------------------
// Init static variables
//-----------------------------------------------------------------------------
int CBaseCombatCharacter : : m_lastInteraction = 0 ;
Relationship_t * * CBaseCombatCharacter : : m_DefaultRelationship = NULL ;
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CCleanupDefaultRelationShips : public CAutoGameSystem
{
public :
CCleanupDefaultRelationShips ( char const * name ) : CAutoGameSystem ( name )
{
}
virtual void Shutdown ( )
{
if ( ! CBaseCombatCharacter : : m_DefaultRelationship )
return ;
for ( int i = 0 ; i < NUM_AI_CLASSES ; + + i )
{
delete [ ] CBaseCombatCharacter : : m_DefaultRelationship [ i ] ;
}
delete [ ] CBaseCombatCharacter : : m_DefaultRelationship ;
CBaseCombatCharacter : : m_DefaultRelationship = NULL ;
}
} ;
static CCleanupDefaultRelationShips g_CleanupDefaultRelationships ( " CCleanupDefaultRelationShips " ) ;
void * SendProxy_SendBaseCombatCharacterLocalDataTable ( const SendProp * pProp , const void * pStruct , const void * pVarData , CSendProxyRecipients * pRecipients , int objectID )
{
// Only send to local player if this is a player
pRecipients - > ClearAllRecipients ( ) ;
CBaseCombatCharacter * pBCC = ( CBaseCombatCharacter * ) pStruct ;
if ( pBCC ! = NULL )
{
if ( pBCC - > IsPlayer ( ) )
{
pRecipients - > SetOnly ( pBCC - > entindex ( ) - 1 ) ;
}
else
{
// If it's a vehicle, send to "driver" (e.g., operator of tf2 manned guns)
IServerVehicle * pVehicle = pBCC - > GetServerVehicle ( ) ;
if ( pVehicle ! = NULL )
{
CBaseCombatCharacter * pDriver = pVehicle - > GetPassenger ( ) ;
if ( pDriver ! = NULL )
{
pRecipients - > SetOnly ( pDriver - > entindex ( ) - 1 ) ;
}
}
}
}
return ( void * ) pVarData ;
}
REGISTER_SEND_PROXY_NON_MODIFIED_POINTER ( SendProxy_SendBaseCombatCharacterLocalDataTable ) ;
// Only send active weapon index to local player
BEGIN_SEND_TABLE_NOBASE ( CBaseCombatCharacter , DT_BCCLocalPlayerExclusive )
SendPropTime ( SENDINFO ( m_flNextAttack ) ) ,
END_SEND_TABLE ( ) ;
//-----------------------------------------------------------------------------
// This table encodes the CBaseCombatCharacter
//-----------------------------------------------------------------------------
IMPLEMENT_SERVERCLASS_ST ( CBaseCombatCharacter , DT_BaseCombatCharacter )
# ifdef GLOWS_ENABLE
SendPropBool ( SENDINFO ( m_bGlowEnabled ) ) ,
# endif // GLOWS_ENABLE
// Data that only gets sent to the local player.
SendPropDataTable ( " bcc_localdata " , 0 , & REFERENCE_SEND_TABLE ( DT_BCCLocalPlayerExclusive ) , SendProxy_SendBaseCombatCharacterLocalDataTable ) ,
SendPropEHandle ( SENDINFO ( m_hActiveWeapon ) ) ,
SendPropArray3 ( SENDINFO_ARRAY3 ( m_hMyWeapons ) , SendPropEHandle ( SENDINFO_ARRAY ( m_hMyWeapons ) ) ) ,
# ifdef INVASION_DLL
SendPropInt ( SENDINFO ( m_iPowerups ) , MAX_POWERUPS , SPROP_UNSIGNED ) ,
# endif
END_SEND_TABLE ( )
//-----------------------------------------------------------------------------
// Interactions
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : InitInteractionSystem ( )
{
// interaction ids continue to go up with every map load, otherwise you get
// collisions if a future map has a different set of NPCs from a current map
}
//-----------------------------------------------------------------------------
// Purpose: Return an interaction ID (so we have no collisions)
//-----------------------------------------------------------------------------
int CBaseCombatCharacter : : GetInteractionID ( void )
{
m_lastInteraction + + ;
return ( m_lastInteraction ) ;
}
// ============================================================================
bool CBaseCombatCharacter : : HasHumanGibs ( void )
{
# if defined( HL2_DLL )
Class_T myClass = Classify ( ) ;
if ( myClass = = CLASS_CITIZEN_PASSIVE | |
myClass = = CLASS_CITIZEN_REBEL | |
myClass = = CLASS_COMBINE | |
myClass = = CLASS_CONSCRIPT | |
myClass = = CLASS_METROPOLICE | |
myClass = = CLASS_PLAYER )
return true ;
# elif defined( HL1_DLL )
Class_T myClass = Classify ( ) ;
if ( myClass = = CLASS_HUMAN_MILITARY | |
myClass = = CLASS_PLAYER_ALLY | |
myClass = = CLASS_HUMAN_PASSIVE | |
myClass = = CLASS_PLAYER )
{
return true ;
}
# elif defined( CSPORT_DLL )
Class_T myClass = Classify ( ) ;
if ( myClass = = CLASS_PLAYER )
{
return true ;
}
# endif
return false ;
}
bool CBaseCombatCharacter : : HasAlienGibs ( void )
{
# if defined( HL2_DLL )
Class_T myClass = Classify ( ) ;
if ( myClass = = CLASS_BARNACLE | |
myClass = = CLASS_STALKER | |
myClass = = CLASS_ZOMBIE | |
myClass = = CLASS_VORTIGAUNT | |
myClass = = CLASS_HEADCRAB )
{
return true ;
}
# elif defined( HL1_DLL )
Class_T myClass = Classify ( ) ;
if ( myClass = = CLASS_ALIEN_MILITARY | |
myClass = = CLASS_ALIEN_MONSTER | |
myClass = = CLASS_INSECT | |
myClass = = CLASS_ALIEN_PREDATOR | |
myClass = = CLASS_ALIEN_PREY )
{
return true ;
}
# endif
return false ;
}
void CBaseCombatCharacter : : CorpseFade ( void )
{
StopAnimation ( ) ;
SetAbsVelocity ( vec3_origin ) ;
SetMoveType ( MOVETYPE_NONE ) ;
SetLocalAngularVelocity ( vec3_angle ) ;
m_flAnimTime = gpGlobals - > curtime ;
IncrementInterpolationFrame ( ) ;
SUB_StartFadeOut ( ) ;
}
//-----------------------------------------------------------------------------
// Visibility caching
//-----------------------------------------------------------------------------
struct VisibilityCacheEntry_t
{
CBaseEntity * pEntity1 ;
CBaseEntity * pEntity2 ;
EHANDLE pBlocker ;
float time ;
} ;
class CVisibilityCacheEntryLess
{
public :
CVisibilityCacheEntryLess ( int ) { }
bool operator ! ( ) const { return false ; }
bool operator ( ) ( const VisibilityCacheEntry_t & lhs , const VisibilityCacheEntry_t & rhs ) const
{
return ( memcmp ( & lhs , & rhs , offsetof ( VisibilityCacheEntry_t , pBlocker ) ) < 0 ) ;
}
} ;
static CUtlRBTree < VisibilityCacheEntry_t , unsigned short , CVisibilityCacheEntryLess > g_VisibilityCache ;
const float VIS_CACHE_ENTRY_LIFE = ( ! IsXbox ( ) ) ? .090 : .500 ;
bool CBaseCombatCharacter : : FVisible ( CBaseEntity * pEntity , int traceMask , CBaseEntity * * ppBlocker )
{
VPROF ( " CBaseCombatCharacter::FVisible " ) ;
if ( traceMask ! = MASK_BLOCKLOS | | ! ShouldUseVisibilityCache ( ) | | pEntity = = this
# if defined(HL2_DLL)
| | Classify ( ) = = CLASS_BULLSEYE | | pEntity - > Classify ( ) = = CLASS_BULLSEYE
# endif
)
{
return BaseClass : : FVisible ( pEntity , traceMask , ppBlocker ) ;
}
VisibilityCacheEntry_t cacheEntry ;
if ( this < pEntity )
{
cacheEntry . pEntity1 = this ;
cacheEntry . pEntity2 = pEntity ;
}
else
{
cacheEntry . pEntity1 = pEntity ;
cacheEntry . pEntity2 = this ;
}
int iCache = g_VisibilityCache . Find ( cacheEntry ) ;
if ( iCache ! = g_VisibilityCache . InvalidIndex ( ) )
{
if ( gpGlobals - > curtime - g_VisibilityCache [ iCache ] . time < VIS_CACHE_ENTRY_LIFE )
{
bool bCachedResult = ! g_VisibilityCache [ iCache ] . pBlocker . IsValid ( ) ;
if ( bCachedResult )
{
if ( ppBlocker )
{
* ppBlocker = g_VisibilityCache [ iCache ] . pBlocker ;
if ( ! * ppBlocker )
{
* ppBlocker = GetWorldEntity ( ) ;
}
}
}
else
{
if ( ppBlocker )
{
* ppBlocker = NULL ;
}
}
return bCachedResult ;
}
}
else
{
if ( g_VisibilityCache . Count ( ) ! = g_VisibilityCache . InvalidIndex ( ) )
{
iCache = g_VisibilityCache . Insert ( cacheEntry ) ;
}
else
{
return BaseClass : : FVisible ( pEntity , traceMask , ppBlocker ) ;
}
}
CBaseEntity * pBlocker = NULL ;
if ( ppBlocker = = NULL )
{
ppBlocker = & pBlocker ;
}
bool bResult = BaseClass : : FVisible ( pEntity , traceMask , ppBlocker ) ;
if ( ! bResult )
{
g_VisibilityCache [ iCache ] . pBlocker = * ppBlocker ;
}
else
{
g_VisibilityCache [ iCache ] . pBlocker = NULL ;
}
g_VisibilityCache [ iCache ] . time = gpGlobals - > curtime ;
return bResult ;
}
void CBaseCombatCharacter : : ResetVisibilityCache ( CBaseCombatCharacter * pBCC )
{
VPROF ( " CBaseCombatCharacter::ResetVisibilityCache " ) ;
if ( ! pBCC )
{
g_VisibilityCache . RemoveAll ( ) ;
return ;
}
int i = g_VisibilityCache . FirstInorder ( ) ;
CUtlVector < unsigned short > removals ;
while ( i ! = g_VisibilityCache . InvalidIndex ( ) )
{
if ( g_VisibilityCache [ i ] . pEntity1 = = pBCC | | g_VisibilityCache [ i ] . pEntity2 = = pBCC )
{
removals . AddToTail ( i ) ;
}
i = g_VisibilityCache . NextInorder ( i ) ;
}
for ( i = 0 ; i < removals . Count ( ) ; i + + )
{
g_VisibilityCache . RemoveAt ( removals [ i ] ) ;
}
}
# ifdef PORTAL
bool CBaseCombatCharacter : : FVisibleThroughPortal ( const CProp_Portal * pPortal , CBaseEntity * pEntity , int traceMask , CBaseEntity * * ppBlocker )
{
VPROF ( " CBaseCombatCharacter::FVisible " ) ;
if ( pEntity - > GetFlags ( ) & FL_NOTARGET )
return false ;
# if HL1_DLL
// FIXME: only block LOS through opaque water
// don't look through water
if ( ( m_nWaterLevel ! = 3 & & pEntity - > m_nWaterLevel = = 3 )
| | ( m_nWaterLevel = = 3 & & pEntity - > m_nWaterLevel = = 0 ) )
return false ;
# endif
Vector vecLookerOrigin = EyePosition ( ) ; //look through the caller's 'eyes'
Vector vecTargetOrigin = pEntity - > EyePosition ( ) ;
// Use the custom LOS trace filter
CTraceFilterLOS traceFilter ( this , COLLISION_GROUP_NONE , pEntity ) ;
Vector vecTranslatedTargetOrigin ;
UTIL_Portal_PointTransform ( pPortal - > m_hLinkedPortal - > MatrixThisToLinked ( ) , vecTargetOrigin , vecTranslatedTargetOrigin ) ;
Ray_t ray ;
ray . Init ( vecLookerOrigin , vecTranslatedTargetOrigin ) ;
trace_t tr ;
// If we're doing an opaque search, include NPCs.
if ( traceMask = = MASK_BLOCKLOS )
{
traceMask = MASK_BLOCKLOS_AND_NPCS ;
}
UTIL_Portal_TraceRay_Bullets ( pPortal , ray , traceMask , & traceFilter , & tr ) ;
if ( tr . fraction ! = 1.0 | | tr . startsolid )
{
// If we hit the entity we're looking for, it's visible
if ( tr . m_pEnt = = pEntity )
return true ;
// Got line of sight on the vehicle the player is driving!
if ( pEntity & & pEntity - > IsPlayer ( ) )
{
CBasePlayer * pPlayer = assert_cast < CBasePlayer * > ( pEntity ) ;
if ( tr . m_pEnt = = pPlayer - > GetVehicleEntity ( ) )
return true ;
}
if ( ppBlocker )
{
* ppBlocker = tr . m_pEnt ;
}
return false ; // Line of sight is not established
}
return true ; // line of sight is valid.
}
# endif
//-----------------------------------------------------------------------------
//=========================================================
// FInViewCone - returns true is the passed ent is in
// the caller's forward view cone. The dot product is performed
// in 2d, making the view cone infinitely tall.
//=========================================================
bool CBaseCombatCharacter : : FInViewCone ( CBaseEntity * pEntity )
{
return FInViewCone ( pEntity - > WorldSpaceCenter ( ) ) ;
}
//=========================================================
// FInViewCone - returns true is the passed Vector is in
// the caller's forward view cone. The dot product is performed
// in 2d, making the view cone infinitely tall.
//=========================================================
bool CBaseCombatCharacter : : FInViewCone ( const Vector & vecSpot )
{
Vector los = ( vecSpot - EyePosition ( ) ) ;
// do this in 2D
los . z = 0 ;
VectorNormalize ( los ) ;
Vector facingDir = EyeDirection2D ( ) ;
float flDot = DotProduct ( los , facingDir ) ;
if ( flDot > m_flFieldOfView )
return true ;
return false ;
}
# ifdef PORTAL
//=========================================================
// FInViewCone - returns true is the passed ent is in
// the caller's forward view cone. The dot product is performed
// in 2d, making the view cone infinitely tall.
//=========================================================
CProp_Portal * CBaseCombatCharacter : : FInViewConeThroughPortal ( CBaseEntity * pEntity )
{
return FInViewConeThroughPortal ( pEntity - > WorldSpaceCenter ( ) ) ;
}
//=========================================================
// FInViewCone - returns true is the passed Vector is in
// the caller's forward view cone. The dot product is performed
// in 2d, making the view cone infinitely tall.
//=========================================================
CProp_Portal * CBaseCombatCharacter : : FInViewConeThroughPortal ( const Vector & vecSpot )
{
int iPortalCount = CProp_Portal_Shared : : AllPortals . Count ( ) ;
if ( iPortalCount = = 0 )
return NULL ;
const Vector ptEyePosition = EyePosition ( ) ;
float fDistToBeat = 1e20 ; //arbitrarily high number
CProp_Portal * pBestPortal = NULL ;
CProp_Portal * * pPortals = CProp_Portal_Shared : : AllPortals . Base ( ) ;
// Check through both portals
for ( int iPortal = 0 ; iPortal < iPortalCount ; + + iPortal )
{
CProp_Portal * pPortal = pPortals [ iPortal ] ;
// Check if this portal is active, linked, and in the view cone
if ( pPortal - > IsActivedAndLinked ( ) & & FInViewCone ( pPortal ) )
{
// The facing direction is the eye to the portal to set up a proper FOV through the relatively small portal hole
Vector facingDir = pPortal - > GetAbsOrigin ( ) - ptEyePosition ;
// If the portal isn't facing the eye, bail
if ( facingDir . Dot ( pPortal - > m_plane_Origin . normal ) > 0.0f )
continue ;
// If the point is behind the linked portal, bail
if ( ( vecSpot - pPortal - > m_hLinkedPortal - > GetAbsOrigin ( ) ) . Dot ( pPortal - > m_hLinkedPortal - > m_plane_Origin . normal ) < 0.0f )
continue ;
// Remove height from the equation
facingDir . z = 0.0f ;
float fPortalDist = VectorNormalize ( facingDir ) ;
// Translate the target spot across the portal
Vector vTranslatedVecSpot ;
UTIL_Portal_PointTransform ( pPortal - > m_hLinkedPortal - > MatrixThisToLinked ( ) , vecSpot , vTranslatedVecSpot ) ;
// do this in 2D
Vector los = ( vTranslatedVecSpot - ptEyePosition ) ;
los . z = 0.0f ;
float fSpotDist = VectorNormalize ( los ) ;
if ( fSpotDist > fDistToBeat )
continue ; //no point in going further, we already have a better portal
// If the target point is closer than the portal (banana juice), bail
// HACK: Extra 32 is a fix for the player who's origin can be on one side of a portal while his center mirrored across is closer than the portal.
if ( fPortalDist > fSpotDist + 32.0f )
continue ;
// Get the worst case FOV from the portal's corners
float fFOVThroughPortal = 1.0f ;
for ( int i = 0 ; i < 4 ; + + i )
{
//Vector vPortalCorner = pPortal->GetAbsOrigin() + vPortalRight * PORTAL_HALF_WIDTH * ( ( i / 2 == 0 ) ? ( 1.0f ) : ( -1.0f ) ) +
// vPortalUp * PORTAL_HALF_HEIGHT * ( ( i % 2 == 0 ) ? ( 1.0f ) : ( -1.0f ) );
Vector vEyeToCorner = pPortal - > m_vPortalCorners [ i ] - ptEyePosition ;
vEyeToCorner . z = 0.0f ;
VectorNormalize ( vEyeToCorner ) ;
float flCornerDot = DotProduct ( vEyeToCorner , facingDir ) ;
if ( flCornerDot < fFOVThroughPortal )
fFOVThroughPortal = flCornerDot ;
}
float flDot = DotProduct ( los , facingDir ) ;
// Use the tougher FOV of either the standard FOV or FOV clipped to the portal hole
if ( flDot > MAX ( fFOVThroughPortal , m_flFieldOfView ) )
{
float fActualDist = ptEyePosition . DistToSqr ( vTranslatedVecSpot ) ;
if ( fActualDist < fDistToBeat )
{
fDistToBeat = fActualDist ;
pBestPortal = pPortal ;
}
}
}
}
return pBestPortal ;
}
# endif
//=========================================================
// FInAimCone - returns true is the passed ent is in
// the caller's forward aim cone. The dot product is performed
// in 2d, making the aim cone infinitely tall.
//=========================================================
bool CBaseCombatCharacter : : FInAimCone ( CBaseEntity * pEntity )
{
return FInAimCone ( pEntity - > BodyTarget ( EyePosition ( ) ) ) ;
}
//=========================================================
// FInAimCone - returns true is the passed Vector is in
// the caller's forward aim cone. The dot product is performed
// in 2d, making the view cone infinitely tall. By default, the
// callers aim cone is assumed to be very narrow
//=========================================================
bool CBaseCombatCharacter : : FInAimCone ( const Vector & vecSpot )
{
Vector los = ( vecSpot - GetAbsOrigin ( ) ) ;
// do this in 2D
los . z = 0 ;
VectorNormalize ( los ) ;
Vector facingDir = BodyDirection2D ( ) ;
float flDot = DotProduct ( los , facingDir ) ;
if ( flDot > 0.994 ) //!!!BUGBUG - magic number same as FacingIdeal(), what is this?
return true ;
return false ;
}
//-----------------------------------------------------------------------------
// Purpose: This is a generic function (to be implemented by sub-classes) to
// handle specific interactions between different types of characters
// (For example the barnacle grabbing an NPC)
// Input : The type of interaction, extra info pointer, and who started it
// Output : true - if sub-class has a response for the interaction
// false - if sub-class has no response
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : HandleInteraction ( int interactionType , void * data , CBaseCombatCharacter * sourceEnt )
{
# ifdef HL2_DLL
if ( interactionType = = g_interactionBarnacleVictimReleased )
{
// For now, throw away the NPC and leave the ragdoll.
UTIL_Remove ( this ) ;
return true ;
}
# endif // HL2_DLL
return false ;
}
//-----------------------------------------------------------------------------
// Purpose: Constructor : Initialize some fields
//-----------------------------------------------------------------------------
CBaseCombatCharacter : : CBaseCombatCharacter ( void )
{
# ifdef _DEBUG
// necessary since in debug, we initialize vectors to NAN for debugging
m_HackedGunPos . Init ( ) ;
# endif
// Zero the damage accumulator.
m_flDamageAccumulator = 0.0f ;
// Init weapon and Ammo data
m_hActiveWeapon = NULL ;
// reset all ammo values to 0
RemoveAllAmmo ( ) ;
// not alive yet
m_aliveTimer . Invalidate ( ) ;
m_hasBeenInjured = 0 ;
for ( int t = 0 ; t < MAX_DAMAGE_TEAMS ; + + t )
{
m_damageHistory [ t ] . team = TEAM_INVALID ;
}
// not standing on a nav area yet
# ifdef MEXT_BOT
m_lastNavArea = NULL ;
# endif
m_registeredNavTeam = TEAM_INVALID ;
for ( int i = 0 ; i < MAX_WEAPONS ; i + + )
{
m_hMyWeapons . Set ( i , NULL ) ;
}
// Default so that spawned entities have this set
m_impactEnergyScale = 1.0f ;
m_bForceServerRagdoll = ai_force_serverside_ragdoll . GetBool ( ) ;
# ifdef GLOWS_ENABLE
m_bGlowEnabled . Set ( false ) ;
# endif // GLOWS_ENABLE
}
//------------------------------------------------------------------------------
// Purpose : Destructor
// Input :
// Output :
//------------------------------------------------------------------------------
CBaseCombatCharacter : : ~ CBaseCombatCharacter ( void )
{
ResetVisibilityCache ( this ) ;
ClearLastKnownArea ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Put the combat character into the environment
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : Spawn ( void )
{
BaseClass : : Spawn ( ) ;
SetBlocksLOS ( false ) ;
m_aliveTimer . Start ( ) ;
m_hasBeenInjured = 0 ;
for ( int t = 0 ; t < MAX_DAMAGE_TEAMS ; + + t )
{
m_damageHistory [ t ] . team = TEAM_INVALID ;
}
// not standing on a nav area yet
ClearLastKnownArea ( ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : Precache ( )
{
BaseClass : : Precache ( ) ;
PrecacheScriptSound ( " BaseCombatCharacter.CorpseGib " ) ;
PrecacheScriptSound ( " BaseCombatCharacter.StopWeaponSounds " ) ;
PrecacheScriptSound ( " BaseCombatCharacter.AmmoPickup " ) ;
for ( int i = m_Relationship . Count ( ) - 1 ; i > = 0 ; i - - )
{
if ( ! m_Relationship [ i ] . entity & & m_Relationship [ i ] . classType = = CLASS_NONE )
{
DevMsg ( 2 , " Removing relationship for lost entity \n " ) ;
m_Relationship . FastRemove ( i ) ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CBaseCombatCharacter : : Restore ( IRestore & restore )
{
int status = BaseClass : : Restore ( restore ) ;
if ( ! status )
return 0 ;
if ( gpGlobals - > eLoadType = = MapLoad_Transition )
{
DevMsg ( 2 , " %s (%s) removing class relationships due to level transition \n " , STRING ( GetEntityName ( ) ) , GetClassname ( ) ) ;
for ( int i = m_Relationship . Count ( ) - 1 ; i > = 0 ; - - i )
{
if ( ! m_Relationship [ i ] . entity & & m_Relationship [ i ] . classType ! = CLASS_NONE )
{
m_Relationship . FastRemove ( i ) ;
}
}
}
return status ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : UpdateOnRemove ( void )
{
int i ;
// Make sure any weapons I didn't drop get removed.
for ( i = 0 ; i < MAX_WEAPONS ; i + + )
{
if ( m_hMyWeapons [ i ] )
{
UTIL_Remove ( m_hMyWeapons [ i ] ) ;
}
}
// tell owner ( if any ) that we're dead.This is mostly for NPCMaker functionality.
CBaseEntity * pOwner = GetOwnerEntity ( ) ;
if ( pOwner )
{
pOwner - > DeathNotice ( this ) ;
SetOwnerEntity ( NULL ) ;
}
# ifdef GLOWS_ENABLE
RemoveGlowEffect ( ) ;
# endif // GLOWS_ENABLE
// Chain at end to mimic destructor unwind order
BaseClass : : UpdateOnRemove ( ) ;
}
//=========================================================
// CorpseGib - create some gore and get rid of a character's
// model.
//=========================================================
bool CBaseCombatCharacter : : CorpseGib ( const CTakeDamageInfo & info )
{
trace_t tr ;
bool gibbed = false ;
EmitSound ( " BaseCombatCharacter.CorpseGib " ) ;
// only humans throw skulls !!!UNDONE - eventually NPCs will have their own sets of gibs
if ( HasHumanGibs ( ) )
{
CGib : : SpawnHeadGib ( this ) ;
CGib : : SpawnRandomGibs ( this , 4 , GIB_HUMAN ) ; // throw some human gibs.
gibbed = true ;
}
else if ( HasAlienGibs ( ) )
{
CGib : : SpawnRandomGibs ( this , 4 , GIB_ALIEN ) ; // Throw alien gibs
gibbed = true ;
}
return gibbed ;
}
//=========================================================
// GetDeathActivity - determines the best type of death
// anim to play.
//=========================================================
Activity CBaseCombatCharacter : : GetDeathActivity ( void )
{
Activity deathActivity ;
bool fTriedDirection ;
float flDot ;
trace_t tr ;
Vector vecSrc ;
if ( IsPlayer ( ) )
{
// die in an interesting way
switch ( random - > RandomInt ( 0 , 7 ) )
{
case 0 : return ACT_DIESIMPLE ;
case 1 : return ACT_DIEBACKWARD ;
case 2 : return ACT_DIEFORWARD ;
case 3 : return ACT_DIEVIOLENT ;
case 4 : return ACT_DIE_HEADSHOT ;
case 5 : return ACT_DIE_CHESTSHOT ;
case 6 : return ACT_DIE_GUTSHOT ;
case 7 : return ACT_DIE_BACKSHOT ;
}
}
vecSrc = WorldSpaceCenter ( ) ;
fTriedDirection = false ;
deathActivity = ACT_DIESIMPLE ; // in case we can't find any special deaths to do.
Vector forward ;
AngleVectors ( GetLocalAngles ( ) , & forward ) ;
flDot = - DotProduct ( forward , g_vecAttackDir ) ;
switch ( m_LastHitGroup )
{
// try to pick a region-specific death.
case HITGROUP_HEAD :
deathActivity = ACT_DIE_HEADSHOT ;
break ;
case HITGROUP_STOMACH :
deathActivity = ACT_DIE_GUTSHOT ;
break ;
case HITGROUP_GENERIC :
// try to pick a death based on attack direction
fTriedDirection = true ;
if ( flDot > 0.3 )
{
deathActivity = ACT_DIEFORWARD ;
}
else if ( flDot < = - 0.3 )
{
deathActivity = ACT_DIEBACKWARD ;
}
break ;
default :
// try to pick a death based on attack direction
fTriedDirection = true ;
if ( flDot > 0.3 )
{
deathActivity = ACT_DIEFORWARD ;
}
else if ( flDot < = - 0.3 )
{
deathActivity = ACT_DIEBACKWARD ;
}
break ;
}
// can we perform the prescribed death?
if ( SelectWeightedSequence ( deathActivity ) = = ACTIVITY_NOT_AVAILABLE )
{
// no! did we fail to perform a directional death?
if ( fTriedDirection )
{
// if yes, we're out of options. Go simple.
deathActivity = ACT_DIESIMPLE ;
}
else
{
// cannot perform the ideal region-specific death, so try a direction.
if ( flDot > 0.3 )
{
deathActivity = ACT_DIEFORWARD ;
}
else if ( flDot < = - 0.3 )
{
deathActivity = ACT_DIEBACKWARD ;
}
}
}
if ( SelectWeightedSequence ( deathActivity ) = = ACTIVITY_NOT_AVAILABLE )
{
// if we're still invalid, simple is our only option.
deathActivity = ACT_DIESIMPLE ;
if ( SelectWeightedSequence ( deathActivity ) = = ACTIVITY_NOT_AVAILABLE )
{
Msg ( " ERROR! %s missing ACT_DIESIMPLE \n " , STRING ( GetModelName ( ) ) ) ;
}
}
if ( deathActivity = = ACT_DIEFORWARD )
{
// make sure there's room to fall forward
UTIL_TraceHull ( vecSrc , vecSrc + forward * 64 , Vector ( - 16 , - 16 , - 18 ) ,
Vector ( 16 , 16 , 18 ) , MASK_SOLID , this , COLLISION_GROUP_NONE , & tr ) ;
if ( tr . fraction ! = 1.0 )
{
deathActivity = ACT_DIESIMPLE ;
}
}
if ( deathActivity = = ACT_DIEBACKWARD )
{
// make sure there's room to fall backward
UTIL_TraceHull ( vecSrc , vecSrc - forward * 64 , Vector ( - 16 , - 16 , - 18 ) ,
Vector ( 16 , 16 , 18 ) , MASK_SOLID , this , COLLISION_GROUP_NONE , & tr ) ;
if ( tr . fraction ! = 1.0 )
{
deathActivity = ACT_DIESIMPLE ;
}
}
return deathActivity ;
}
// UNDONE: Should these operate on a list of weapon/items
Activity CBaseCombatCharacter : : Weapon_TranslateActivity ( Activity baseAct , bool * pRequired )
{
Activity translated = baseAct ;
if ( m_hActiveWeapon )
{
translated = m_hActiveWeapon - > ActivityOverride ( baseAct , pRequired ) ;
}
else if ( pRequired )
{
* pRequired = false ;
}
return translated ;
}
//-----------------------------------------------------------------------------
// Purpose: NPCs should override this function to translate activities
// such as ACT_WALK, etc.
// Input :
// Output :
//-----------------------------------------------------------------------------
Activity CBaseCombatCharacter : : NPC_TranslateActivity ( Activity baseAct )
{
return baseAct ;
}
void CBaseCombatCharacter : : Weapon_SetActivity ( Activity newActivity , float duration )
{
if ( m_hActiveWeapon )
{
m_hActiveWeapon - > SetActivity ( newActivity , duration ) ;
}
}
void CBaseCombatCharacter : : Weapon_FrameUpdate ( void )
{
if ( m_hActiveWeapon )
{
m_hActiveWeapon - > Operator_FrameUpdate ( this ) ;
}
}
//------------------------------------------------------------------------------
// Purpose : expects a length to trace, amount
// of damage to do, and damage type. Returns a pointer to
// the damaged entity in case the NPC wishes to do
// other stuff to the victim (punchangle, etc)
//
// Used for many contact-range melee attacks. Bites, claws, etc.
// Input :
// Output :
//------------------------------------------------------------------------------
CBaseEntity * CBaseCombatCharacter : : CheckTraceHullAttack ( float flDist , const Vector & mins , const Vector & maxs , int iDamage , int iDmgType , float forceScale , bool bDamageAnyNPC )
{
// If only a length is given assume we want to trace in our facing direction
Vector forward ;
AngleVectors ( GetAbsAngles ( ) , & forward ) ;
Vector vStart = GetAbsOrigin ( ) ;
// The ideal place to start the trace is in the center of the attacker's bounding box.
// however, we need to make sure there's enough clearance. Some of the smaller monsters aren't
// as big as the hull we try to trace with. (SJB)
float flVerticalOffset = WorldAlignSize ( ) . z * 0.5 ;
if ( flVerticalOffset < maxs . z )
{
// There isn't enough room to trace this hull, it's going to drag the ground.
// so make the vertical offset just enough to clear the ground.
flVerticalOffset = maxs . z + 1.0 ;
}
vStart . z + = flVerticalOffset ;
Vector vEnd = vStart + ( forward * flDist ) ;
return CheckTraceHullAttack ( vStart , vEnd , mins , maxs , iDamage , iDmgType , forceScale , bDamageAnyNPC ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pHandleEntity -
// contentsMask -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CTraceFilterMelee : : ShouldHitEntity ( IHandleEntity * pHandleEntity , int contentsMask )
{
if ( ! StandardFilterRules ( pHandleEntity , contentsMask ) )
return false ;
if ( ! PassServerEntityFilter ( pHandleEntity , m_pPassEnt ) )
return false ;
// Don't test if the game code tells us we should ignore this collision...
CBaseEntity * pEntity = EntityFromEntityHandle ( pHandleEntity ) ;
if ( pEntity )
{
if ( ! pEntity - > ShouldCollide ( m_collisionGroup , contentsMask ) )
return false ;
if ( ! g_pGameRules - > ShouldCollide ( m_collisionGroup , pEntity - > GetCollisionGroup ( ) ) )
return false ;
if ( pEntity - > m_takedamage = = DAMAGE_NO )
return false ;
// FIXME: Do not translate this to the driver because the driver only accepts damage from the vehicle
// Translate the vehicle into its driver for damage
/*
if ( pEntity - > GetServerVehicle ( ) ! = NULL )
{
CBaseEntity * pDriver = pEntity - > GetServerVehicle ( ) - > GetPassenger ( ) ;
if ( pDriver ! = NULL )
{
pEntity = pDriver ;
}
}
*/
Vector attackDir = pEntity - > WorldSpaceCenter ( ) - m_dmgInfo - > GetAttacker ( ) - > WorldSpaceCenter ( ) ;
VectorNormalize ( attackDir ) ;
CTakeDamageInfo info = ( * m_dmgInfo ) ;
CalculateMeleeDamageForce ( & info , attackDir , info . GetAttacker ( ) - > WorldSpaceCenter ( ) , m_flForceScale ) ;
CBaseCombatCharacter * pBCC = info . GetAttacker ( ) - > MyCombatCharacterPointer ( ) ;
CBaseCombatCharacter * pVictimBCC = pEntity - > MyCombatCharacterPointer ( ) ;
// Only do these comparisons between NPCs
if ( pBCC & & pVictimBCC )
{
// Can only damage other NPCs that we hate
if ( m_bDamageAnyNPC | | pBCC - > IRelationType ( pEntity ) = = D_HT )
{
if ( info . GetDamage ( ) )
{
pEntity - > TakeDamage ( info ) ;
}
// Put a combat sound in
CSoundEnt : : InsertSound ( SOUND_COMBAT , info . GetDamagePosition ( ) , 200 , 0.2f , info . GetAttacker ( ) ) ;
m_pHit = pEntity ;
return true ;
}
}
else
{
m_pHit = pEntity ;
// Make sure if the player is holding this, he drops it
Pickup_ForcePlayerToDropThisObject ( pEntity ) ;
// Otherwise just damage passive objects in our way
if ( info . GetDamage ( ) )
{
pEntity - > TakeDamage ( info ) ;
}
}
}
return false ;
}
//------------------------------------------------------------------------------
// Purpose : start and end trace position, amount
// of damage to do, and damage type. Returns a pointer to
// the damaged entity in case the NPC wishes to do
// other stuff to the victim (punchangle, etc)
//
// Used for many contact-range melee attacks. Bites, claws, etc.
// Input :
// Output :
//------------------------------------------------------------------------------
CBaseEntity * CBaseCombatCharacter : : CheckTraceHullAttack ( const Vector & vStart , const Vector & vEnd , const Vector & mins , const Vector & maxs , int iDamage , int iDmgType , float flForceScale , bool bDamageAnyNPC )
{
// Handy debuging tool to visualize HullAttack trace
if ( ai_show_hull_attacks . GetBool ( ) )
{
float length = ( vEnd - vStart ) . Length ( ) ;
Vector direction = ( vEnd - vStart ) ;
VectorNormalize ( direction ) ;
Vector hullMaxs = maxs ;
hullMaxs . x = length + hullMaxs . x ;
NDebugOverlay : : BoxDirection ( vStart , mins , hullMaxs , direction , 100 , 255 , 255 , 20 , 1.0 ) ;
NDebugOverlay : : BoxDirection ( vStart , mins , maxs , direction , 255 , 0 , 0 , 20 , 1.0 ) ;
}
# if 1
CTakeDamageInfo dmgInfo ( this , this , iDamage , iDmgType ) ;
// COLLISION_GROUP_PROJECTILE does some handy filtering that's very appropriate for this type of attack, as well. (sjb) 7/25/2007
CTraceFilterMelee traceFilter ( this , COLLISION_GROUP_PROJECTILE , & dmgInfo , flForceScale , bDamageAnyNPC ) ;
Ray_t ray ;
ray . Init ( vStart , vEnd , mins , maxs ) ;
trace_t tr ;
enginetrace - > TraceRay ( ray , MASK_SHOT_HULL , & traceFilter , & tr ) ;
CBaseEntity * pEntity = traceFilter . m_pHit ;
if ( pEntity = = NULL )
{
// See if perhaps I'm trying to claw/bash someone who is standing on my head.
Vector vecTopCenter ;
Vector vecEnd ;
Vector vecMins , vecMaxs ;
// Do a tracehull from the top center of my bounding box.
vecTopCenter = GetAbsOrigin ( ) ;
CollisionProp ( ) - > WorldSpaceAABB ( & vecMins , & vecMaxs ) ;
vecTopCenter . z = vecMaxs . z + 1.0f ;
vecEnd = vecTopCenter ;
vecEnd . z + = 2.0f ;
ray . Init ( vecTopCenter , vEnd , mins , maxs ) ;
enginetrace - > TraceRay ( ray , MASK_SHOT_HULL , & traceFilter , & tr ) ;
pEntity = traceFilter . m_pHit ;
}
if ( pEntity & & ! pEntity - > CanBeHitByMeleeAttack ( this ) )
{
// If we touched something, but it shouldn't be hit, return nothing.
pEntity = NULL ;
}
return pEntity ;
# else
trace_t tr ;
UTIL_TraceHull ( vStart , vEnd , mins , maxs , MASK_SHOT_HULL , this , COLLISION_GROUP_NONE , & tr ) ;
CBaseEntity * pEntity = tr . m_pEnt ;
if ( ! pEntity )
{
// See if perhaps I'm trying to claw/bash someone who is standing on my head.
Vector vecTopCenter ;
Vector vecEnd ;
Vector vecMins , vecMaxs ;
// Do a tracehull from the top center of my bounding box.
vecTopCenter = GetAbsOrigin ( ) ;
CollisionProp ( ) - > WorldSpaceAABB ( & vecMins , & vecMaxs ) ;
vecTopCenter . z = vecMaxs . z + 1.0f ;
vecEnd = vecTopCenter ;
vecEnd . z + = 2.0f ;
UTIL_TraceHull ( vecTopCenter , vecEnd , mins , maxs , MASK_SHOT_HULL , this , COLLISION_GROUP_NONE , & tr ) ;
pEntity = tr . m_pEnt ;
}
if ( ! pEntity | | ! pEntity - > m_takedamage | | ! pEntity - > IsAlive ( ) )
return NULL ;
// Translate the vehicle into its driver for damage
if ( pEntity - > GetServerVehicle ( ) ! = NULL )
{
CBaseEntity * pDriver = pEntity - > GetServerVehicle ( ) - > GetPassenger ( ) ;
if ( pDriver ! = NULL )
{
pEntity = pDriver ;
//FIXME: Hook for damage scale in car here
}
}
// Must hate the hit entity
if ( IRelationType ( pEntity ) = = D_HT )
{
if ( iDamage > 0 )
{
CTakeDamageInfo info ( this , this , iDamage , iDmgType ) ;
CalculateMeleeDamageForce ( & info , ( vEnd - vStart ) , vStart , forceScale ) ;
pEntity - > TakeDamage ( info ) ;
}
}
return pEntity ;
# endif
}
bool CBaseCombatCharacter : : Event_Gibbed ( const CTakeDamageInfo & info )
{
bool fade = false ;
if ( HasHumanGibs ( ) )
{
ConVarRef violence_hgibs ( " violence_hgibs " ) ;
if ( violence_hgibs . IsValid ( ) & & violence_hgibs . GetInt ( ) = = 0 )
{
fade = true ;
}
}
else if ( HasAlienGibs ( ) )
{
ConVarRef violence_agibs ( " violence_agibs " ) ;
if ( violence_agibs . IsValid ( ) & & violence_agibs . GetInt ( ) = = 0 )
{
fade = true ;
}
}
m_takedamage = DAMAGE_NO ;
AddSolidFlags ( FSOLID_NOT_SOLID ) ;
m_lifeState = LIFE_DEAD ;
if ( fade )
{
CorpseFade ( ) ;
return false ;
}
else
{
AddEffects ( EF_NODRAW ) ; // make the model invisible.
return CorpseGib ( info ) ;
}
}
Vector CBaseCombatCharacter : : CalcDamageForceVector ( const CTakeDamageInfo & info )
{
// Already have a damage force in the data, use that.
bool bNoPhysicsForceDamage = g_pGameRules - > Damage_NoPhysicsForce ( info . GetDamageType ( ) ) ;
if ( info . GetDamageForce ( ) ! = vec3_origin | | bNoPhysicsForceDamage )
{
if ( info . GetDamageType ( ) & DMG_BLAST )
{
// Fudge blast forces a little bit, so that each
// victim gets a slightly different trajectory.
// This simulates features that usually vary from
// person-to-person variables such as bodyweight,
// which are all indentical for characters using the same model.
float scale = random - > RandomFloat ( 0.85 , 1.15 ) ;
Vector force = info . GetDamageForce ( ) ;
force . x * = scale ;
force . y * = scale ;
// Try to always exaggerate the upward force because we've got pretty harsh gravity
force . z * = ( force . z > 0 ) ? 1.15 : scale ;
return force ;
}
return info . GetDamageForce ( ) ;
}
CBaseEntity * pForce = info . GetInflictor ( ) ;
if ( ! pForce )
{
pForce = info . GetAttacker ( ) ;
}
if ( pForce )
{
// Calculate an impulse large enough to push a 75kg man 4 in/sec per point of damage
float forceScale = info . GetDamage ( ) * 75 * 4 ;
Vector forceVector ;
// If the damage is a blast, point the force vector higher than usual, this gives
// the ragdolls a bodacious "really got blowed up" look.
if ( info . GetDamageType ( ) & DMG_BLAST )
{
// exaggerate the force from explosions a little (37.5%)
forceVector = ( GetLocalOrigin ( ) + Vector ( 0 , 0 , WorldAlignSize ( ) . z ) ) - pForce - > GetLocalOrigin ( ) ;
VectorNormalize ( forceVector ) ;
forceVector * = 1.375f ;
}
else
{
// taking damage from self? Take a little random force, but still try to collapse on the spot.
if ( this = = pForce )
{
forceVector . x = random - > RandomFloat ( - 1.0f , 1.0f ) ;
forceVector . y = random - > RandomFloat ( - 1.0f , 1.0f ) ;
forceVector . z = 0.0 ;
forceScale = random - > RandomFloat ( 1000.0f , 2000.0f ) ;
}
else
{
// UNDONE: Collision forces are baked in to CTakeDamageInfo now
// UNDONE: Is this MOVETYPE_VPHYSICS code still necessary?
if ( pForce - > GetMoveType ( ) = = MOVETYPE_VPHYSICS )
{
// killed by a physics object
IPhysicsObject * pPhysics = VPhysicsGetObject ( ) ;
if ( ! pPhysics )
{
pPhysics = pForce - > VPhysicsGetObject ( ) ;
}
pPhysics - > GetVelocity ( & forceVector , NULL ) ;
forceScale = pPhysics - > GetMass ( ) ;
}
else
{
forceVector = GetLocalOrigin ( ) - pForce - > GetLocalOrigin ( ) ;
VectorNormalize ( forceVector ) ;
}
}
}
return forceVector * forceScale ;
}
return vec3_origin ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : FixupBurningServerRagdoll ( CBaseEntity * pRagdoll )
{
if ( ! IsOnFire ( ) )
return ;
// Move the fire effects entity to the ragdoll
CEntityFlame * pFireChild = dynamic_cast < CEntityFlame * > ( GetEffectEntity ( ) ) ;
if ( pFireChild )
{
SetEffectEntity ( NULL ) ;
pRagdoll - > AddFlag ( FL_ONFIRE ) ;
pFireChild - > SetAbsOrigin ( pRagdoll - > GetAbsOrigin ( ) ) ;
pFireChild - > AttachToEntity ( pRagdoll ) ;
pFireChild - > AddEFlags ( EFL_FORCE_CHECK_TRANSMIT ) ;
pRagdoll - > SetEffectEntity ( pFireChild ) ;
color32 color = GetRenderColor ( ) ;
pRagdoll - > SetRenderColor ( color . r , color . g , color . b ) ;
}
}
bool CBaseCombatCharacter : : BecomeRagdollBoogie ( CBaseEntity * pKiller , const Vector & forceVector , float duration , int flags )
{
Assert ( CanBecomeRagdoll ( ) ) ;
CTakeDamageInfo info ( pKiller , pKiller , 1.0f , DMG_GENERIC ) ;
info . SetDamageForce ( forceVector ) ;
CBaseEntity * pRagdoll = CreateServerRagdoll ( this , 0 , info , COLLISION_GROUP_INTERACTIVE_DEBRIS , true ) ;
pRagdoll - > SetCollisionBounds ( CollisionProp ( ) - > OBBMins ( ) , CollisionProp ( ) - > OBBMaxs ( ) ) ;
CRagdollBoogie : : Create ( pRagdoll , 200 , gpGlobals - > curtime , duration , flags ) ;
CTakeDamageInfo ragdollInfo ( pKiller , pKiller , 10000.0 , DMG_GENERIC | DMG_REMOVENORAGDOLL ) ;
ragdollInfo . SetDamagePosition ( WorldSpaceCenter ( ) ) ;
ragdollInfo . SetDamageForce ( Vector ( 0 , 0 , 1 ) ) ;
TakeDamage ( ragdollInfo ) ;
return true ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : BecomeRagdoll ( const CTakeDamageInfo & info , const Vector & forceVector )
{
if ( ( info . GetDamageType ( ) & DMG_VEHICLE ) & & ! g_pGameRules - > IsMultiplayer ( ) )
{
CTakeDamageInfo info2 = info ;
info2 . SetDamageForce ( forceVector ) ;
Vector pos = info2 . GetDamagePosition ( ) ;
float flAbsMinsZ = GetAbsOrigin ( ) . z + WorldAlignMins ( ) . z ;
if ( ( pos . z - flAbsMinsZ ) < 24 )
{
// HACKHACK: Make sure the vehicle impact is at least 2ft off the ground
pos . z = flAbsMinsZ + 24 ;
info2 . SetDamagePosition ( pos ) ;
}
// UNDONE: Put in a real sound cue here, don't do this bogus hack anymore
#if 0
Vector soundOrigin = info . GetDamagePosition ( ) ;
CPASAttenuationFilter filter ( soundOrigin ) ;
EmitSound_t ep ;
ep . m_nChannel = CHAN_STATIC ;
ep . m_pSoundName = " NPC_MetroPolice.HitByVehicle " ;
ep . m_flVolume = 1.0f ;
ep . m_SoundLevel = SNDLVL_NORM ;
ep . m_pOrigin = & soundOrigin ;
EmitSound ( filter , SOUND_FROM_WORLD , ep ) ;
# endif
// in single player create ragdolls on the server when the player hits someone
// with their vehicle - for more dramatic death/collisions
CBaseEntity * pRagdoll = CreateServerRagdoll ( this , m_nForceBone , info2 , COLLISION_GROUP_INTERACTIVE_DEBRIS , true ) ;
FixupBurningServerRagdoll ( pRagdoll ) ;
RemoveDeferred ( ) ;
return true ;
}
//Fix up the force applied to server side ragdolls. This fixes magnets not affecting them.
CTakeDamageInfo newinfo = info ;
newinfo . SetDamageForce ( forceVector ) ;
# ifdef HL2_EPISODIC
// Burning corpses are server-side in episodic, if we're in darkness mode
if ( IsOnFire ( ) & & HL2GameRules ( ) - > IsAlyxInDarknessMode ( ) )
{
CBaseEntity * pRagdoll = CreateServerRagdoll ( this , m_nForceBone , newinfo , COLLISION_GROUP_DEBRIS ) ;
FixupBurningServerRagdoll ( pRagdoll ) ;
RemoveDeferred ( ) ;
return true ;
}
# endif
# ifdef HL2_DLL
bool bMegaPhyscannonActive = false ;
# if !defined( HL2MP )
bMegaPhyscannonActive = HL2GameRules ( ) - > MegaPhyscannonActive ( ) ;
# endif // !HL2MP
// Mega physgun requires everything to be a server-side ragdoll
if ( m_bForceServerRagdoll = = true | | ( ( bMegaPhyscannonActive = = true ) & & ! IsPlayer ( ) & & Classify ( ) ! = CLASS_PLAYER_ALLY_VITAL & & Classify ( ) ! = CLASS_PLAYER_ALLY ) )
{
if ( CanBecomeServerRagdoll ( ) = = false )
return false ;
//FIXME: This is fairly leafy to be here, but time is short!
CBaseEntity * pRagdoll = CreateServerRagdoll ( this , m_nForceBone , newinfo , COLLISION_GROUP_INTERACTIVE_DEBRIS , true ) ;
FixupBurningServerRagdoll ( pRagdoll ) ;
PhysSetEntityGameFlags ( pRagdoll , FVPHYSICS_NO_SELF_COLLISIONS ) ;
RemoveDeferred ( ) ;
return true ;
}
if ( hl2_episodic . GetBool ( ) & & Classify ( ) = = CLASS_PLAYER_ALLY_VITAL )
{
CreateServerRagdoll ( this , m_nForceBone , newinfo , COLLISION_GROUP_INTERACTIVE_DEBRIS , true ) ;
RemoveDeferred ( ) ;
return true ;
}
# endif //HL2_DLL
return BecomeRagdollOnClient ( forceVector ) ;
}
/*
= = = = = = = = = = = =
Killed
= = = = = = = = = = = =
*/
void CBaseCombatCharacter : : Event_Killed ( const CTakeDamageInfo & info )
{
extern ConVar npc_vphysics ;
// Advance life state to dying
m_lifeState = LIFE_DYING ;
// Calculate death force
Vector forceVector = CalcDamageForceVector ( info ) ;
// See if there's a ragdoll magnet that should influence our force.
CRagdollMagnet * pMagnet = CRagdollMagnet : : FindBestMagnet ( this ) ;
if ( pMagnet )
{
forceVector + = pMagnet - > GetForceVector ( this ) ;
}
CBaseCombatWeapon * pDroppedWeapon = m_hActiveWeapon . Get ( ) ;
// Drop any weapon that I own
if ( VPhysicsGetObject ( ) )
{
Vector weaponForce = forceVector * VPhysicsGetObject ( ) - > GetInvMass ( ) ;
Weapon_Drop ( m_hActiveWeapon , NULL , & weaponForce ) ;
}
else
{
Weapon_Drop ( m_hActiveWeapon ) ;
}
// if flagged to drop a health kit
if ( HasSpawnFlags ( SF_NPC_DROP_HEALTHKIT ) )
{
CBaseEntity : : Create ( " item_healthvial " , GetAbsOrigin ( ) , GetAbsAngles ( ) ) ;
}
// clear the deceased's sound channels.(may have been firing or reloading when killed)
EmitSound ( " BaseCombatCharacter.StopWeaponSounds " ) ;
// Tell my killer that he got me!
if ( info . GetAttacker ( ) )
{
info . GetAttacker ( ) - > Event_KilledOther ( this , info ) ;
g_EventQueue . AddEvent ( info . GetAttacker ( ) , " KilledNPC " , 0.3 , this , this ) ;
}
SendOnKilledGameEvent ( info ) ;
// Ragdoll unless we've gibbed
if ( ShouldGib ( info ) = = false )
{
bool bRagdollCreated = false ;
if ( ( info . GetDamageType ( ) & DMG_DISSOLVE ) & & CanBecomeRagdoll ( ) )
{
int nDissolveType = ENTITY_DISSOLVE_NORMAL ;
if ( info . GetDamageType ( ) & DMG_SHOCK )
{
nDissolveType = ENTITY_DISSOLVE_ELECTRICAL ;
}
bRagdollCreated = Dissolve ( NULL , gpGlobals - > curtime , false , nDissolveType ) ;
// Also dissolve any weapons we dropped
if ( pDroppedWeapon )
{
pDroppedWeapon - > Dissolve ( NULL , gpGlobals - > curtime , false , nDissolveType ) ;
}
}
# ifdef HL2_DLL
else if ( PlayerHasMegaPhysCannon ( ) )
{
if ( pDroppedWeapon )
{
pDroppedWeapon - > Dissolve ( NULL , gpGlobals - > curtime , false , ENTITY_DISSOLVE_NORMAL ) ;
}
}
# endif
if ( ! bRagdollCreated & & ( info . GetDamageType ( ) & DMG_REMOVENORAGDOLL ) = = 0 )
{
BecomeRagdoll ( info , forceVector ) ;
}
}
// no longer standing on a nav area
ClearLastKnownArea ( ) ;
#if 0
// L4D specific hack for zombie commentary mode
if ( GetOwnerEntity ( ) ! = NULL )
{
GetOwnerEntity ( ) - > DeathNotice ( this ) ;
}
# endif
# ifdef NEXT_BOT
// inform bots
TheNextBots ( ) . OnKilled ( this , info ) ;
# endif
# ifdef GLOWS_ENABLE
RemoveGlowEffect ( ) ;
# endif // GLOWS_ENABLE
}
void CBaseCombatCharacter : : Event_Dying ( const CTakeDamageInfo & info )
{
}
void CBaseCombatCharacter : : Event_Dying ( )
{
CTakeDamageInfo info ;
Event_Dying ( info ) ;
}
// ===========================================================================
// > Weapons
// ===========================================================================
bool CBaseCombatCharacter : : Weapon_Detach ( CBaseCombatWeapon * pWeapon )
{
for ( int i = 0 ; i < MAX_WEAPONS ; i + + )
{
if ( pWeapon = = m_hMyWeapons [ i ] )
{
pWeapon - > Detach ( ) ;
if ( pWeapon - > HolsterOnDetach ( ) )
{
pWeapon - > Holster ( ) ;
}
m_hMyWeapons . Set ( i , NULL ) ;
pWeapon - > SetOwner ( NULL ) ;
if ( pWeapon = = m_hActiveWeapon )
ClearActiveWeapon ( ) ;
return true ;
}
}
return false ;
}
//-----------------------------------------------------------------------------
// For weapon strip
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : ThrowDirForWeaponStrip ( CBaseCombatWeapon * pWeapon , const Vector & vecForward , Vector * pVecThrowDir )
{
// HACK! Always throw the physcannon directly in front of the player
// This is necessary for the physgun upgrade scene.
if ( FClassnameIs ( pWeapon , " weapon_physcannon " ) )
{
if ( hl2_episodic . GetBool ( ) )
{
// It has been discovered that it's possible to throw the physcannon out of the world this way.
// So try to find a direction to throw the physcannon that's legal.
Vector vecOrigin = EyePosition ( ) ;
Vector vecRight ;
CrossProduct ( vecForward , Vector ( 0 , 0 , 1 ) , vecRight ) ;
Vector vecTest [ 4 ] ;
vecTest [ 0 ] = vecForward ;
vecTest [ 1 ] = - vecForward ;
vecTest [ 2 ] = vecRight ;
vecTest [ 3 ] = - vecRight ;
trace_t tr ;
int i ;
for ( i = 0 ; i < 4 ; i + + )
{
UTIL_TraceLine ( vecOrigin , vecOrigin + vecTest [ i ] * 48.0f , MASK_SOLID_BRUSHONLY , this , COLLISION_GROUP_NONE , & tr ) ;
if ( ! tr . startsolid & & tr . fraction = = 1.0f )
{
* pVecThrowDir = vecTest [ i ] ;
return ;
}
}
}
// Well, fall through to what we did before we tried to make this a bit more robust.
* pVecThrowDir = vecForward ;
}
else
{
// Nowhere in particular; just drop it.
VMatrix zRot ;
MatrixBuildRotateZ ( zRot , random - > RandomFloat ( - 60.0f , 60.0f ) ) ;
Vector vecThrow ;
Vector3DMultiply ( zRot , vecForward , * pVecThrowDir ) ;
pVecThrowDir - > z = random - > RandomFloat ( - 0.5f , 0.5f ) ;
VectorNormalize ( * pVecThrowDir ) ;
}
}
//-----------------------------------------------------------------------------
// For weapon strip
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : DropWeaponForWeaponStrip ( CBaseCombatWeapon * pWeapon ,
const Vector & vecForward , const QAngle & vecAngles , float flDiameter )
{
Vector vecOrigin ;
CollisionProp ( ) - > RandomPointInBounds ( Vector ( 0.5f , 0.5f , 0.5f ) , Vector ( 0.5f , 0.5f , 1.0f ) , & vecOrigin ) ;
// Nowhere in particular; just drop it.
Vector vecThrow ;
ThrowDirForWeaponStrip ( pWeapon , vecForward , & vecThrow ) ;
Vector vecOffsetOrigin ;
VectorMA ( vecOrigin , flDiameter , vecThrow , vecOffsetOrigin ) ;
trace_t tr ;
UTIL_TraceLine ( vecOrigin , vecOffsetOrigin , MASK_SOLID_BRUSHONLY , this , COLLISION_GROUP_NONE , & tr ) ;
if ( tr . startsolid | | tr . allsolid | | ( tr . fraction < 1.0f & & tr . m_pEnt ! = pWeapon ) )
{
//FIXME: Throw towards a known safe spot?
vecThrow . Negate ( ) ;
VectorMA ( vecOrigin , flDiameter , vecThrow , vecOffsetOrigin ) ;
}
vecThrow * = random - > RandomFloat ( 400.0f , 600.0f ) ;
pWeapon - > SetAbsOrigin ( vecOrigin ) ;
pWeapon - > SetAbsAngles ( vecAngles ) ;
pWeapon - > Drop ( vecThrow ) ;
pWeapon - > SetRemoveable ( false ) ;
Weapon_Detach ( pWeapon ) ;
}
//-----------------------------------------------------------------------------
// For weapon strip
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : Weapon_DropAll ( bool bDisallowWeaponPickup )
{
if ( GetFlags ( ) & FL_NPC )
{
for ( int i = 0 ; i < MAX_WEAPONS ; + + i )
{
CBaseCombatWeapon * pWeapon = m_hMyWeapons [ i ] ;
if ( ! pWeapon )
continue ;
Weapon_Drop ( pWeapon ) ;
}
return ;
}
QAngle gunAngles ;
VectorAngles ( BodyDirection2D ( ) , gunAngles ) ;
Vector vecForward ;
AngleVectors ( gunAngles , & vecForward , NULL , NULL ) ;
float flDiameter = sqrt ( CollisionProp ( ) - > OBBSize ( ) . x * CollisionProp ( ) - > OBBSize ( ) . x +
CollisionProp ( ) - > OBBSize ( ) . y * CollisionProp ( ) - > OBBSize ( ) . y ) ;
CBaseCombatWeapon * pActiveWeapon = GetActiveWeapon ( ) ;
for ( int i = 0 ; i < MAX_WEAPONS ; + + i )
{
CBaseCombatWeapon * pWeapon = m_hMyWeapons [ i ] ;
if ( ! pWeapon )
continue ;
// Have to drop this after we've dropped everything else, so autoswitch doesn't happen
if ( pWeapon = = pActiveWeapon )
continue ;
DropWeaponForWeaponStrip ( pWeapon , vecForward , gunAngles , flDiameter ) ;
// HACK: This hack is required to allow weapons to be disintegrated
// in the citadel weapon-strip scene
// Make them not pick-uppable again. This also has the effect of allowing weapons
// to collide with triggers.
if ( bDisallowWeaponPickup )
{
pWeapon - > RemoveSolidFlags ( FSOLID_TRIGGER ) ;
IPhysicsObject * pObj = pWeapon - > VPhysicsGetObject ( ) ;
if ( pObj ! = NULL )
{
pObj - > SetGameFlags ( FVPHYSICS_NO_PLAYER_PICKUP ) ;
}
}
}
// Drop the active weapon normally...
if ( pActiveWeapon )
{
// Nowhere in particular; just drop it.
Vector vecThrow ;
ThrowDirForWeaponStrip ( pActiveWeapon , vecForward , & vecThrow ) ;
// Throw a little more vigorously; it starts closer to the player
vecThrow * = random - > RandomFloat ( 800.0f , 1000.0f ) ;
Weapon_Drop ( pActiveWeapon , NULL , & vecThrow ) ;
pActiveWeapon - > SetRemoveable ( false ) ;
// HACK: This hack is required to allow weapons to be disintegrated
// in the citadel weapon-strip scene
// Make them not pick-uppable again. This also has the effect of allowing weapons
// to collide with triggers.
if ( bDisallowWeaponPickup )
{
pActiveWeapon - > RemoveSolidFlags ( FSOLID_TRIGGER ) ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Drop the active weapon, optionally throwing it at the given target position.
// Input : pWeapon - Weapon to drop/throw.
// pvecTarget - Position to throw it at, NULL for none.
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : Weapon_Drop ( CBaseCombatWeapon * pWeapon , const Vector * pvecTarget /* = NULL */ , const Vector * pVelocity /* = NULL */ )
{
if ( ! pWeapon )
return ;
// If I'm an NPC, fill the weapon with ammo before I drop it.
if ( GetFlags ( ) & FL_NPC )
{
if ( pWeapon - > UsesClipsForAmmo1 ( ) )
{
pWeapon - > m_iClip1 = pWeapon - > GetDefaultClip1 ( ) ;
if ( FClassnameIs ( pWeapon , " weapon_smg1 " ) )
{
// Drop enough ammo to kill 2 of me.
// Figure out how much damage one piece of this type of ammo does to this type of enemy.
float flAmmoDamage = g_pGameRules - > GetAmmoDamage ( UTIL_PlayerByIndex ( 1 ) , this , pWeapon - > GetPrimaryAmmoType ( ) ) ;
pWeapon - > m_iClip1 = ( GetMaxHealth ( ) / flAmmoDamage ) * 2 ;
}
}
if ( pWeapon - > UsesClipsForAmmo2 ( ) )
{
pWeapon - > m_iClip2 = pWeapon - > GetDefaultClip2 ( ) ;
}
if ( IsXbox ( ) )
{
pWeapon - > AddEffects ( EF_ITEM_BLINK ) ;
}
}
if ( IsPlayer ( ) )
{
Vector vThrowPos = Weapon_ShootPosition ( ) - Vector ( 0 , 0 , 12 ) ;
if ( UTIL_PointContents ( vThrowPos ) & CONTENTS_SOLID )
{
Msg ( " Weapon spawning in solid! \n " ) ;
}
pWeapon - > SetAbsOrigin ( vThrowPos ) ;
QAngle gunAngles ;
VectorAngles ( BodyDirection2D ( ) , gunAngles ) ;
pWeapon - > SetAbsAngles ( gunAngles ) ;
}
else
{
int iBIndex = - 1 ;
int iWeaponBoneIndex = - 1 ;
CStudioHdr * hdr = pWeapon - > GetModelPtr ( ) ;
// If I have a hand, set the weapon position to my hand bone position.
if ( hdr & & hdr - > numbones ( ) > 0 )
{
// Assume bone zero is the root
for ( iWeaponBoneIndex = 0 ; iWeaponBoneIndex < hdr - > numbones ( ) ; + + iWeaponBoneIndex )
{
iBIndex = LookupBone ( hdr - > pBone ( iWeaponBoneIndex ) - > pszName ( ) ) ;
// Found one!
if ( iBIndex ! = - 1 )
{
break ;
}
}
if ( iBIndex = = - 1 )
{
iBIndex = LookupBone ( " ValveBiped.Weapon_bone " ) ;
}
}
else
{
iBIndex = LookupBone ( " ValveBiped.Weapon_bone " ) ;
}
if ( iBIndex ! = - 1 )
{
Vector origin ;
QAngle angles ;
matrix3x4_t transform ;
// Get the transform for the weapon bonetoworldspace in the NPC
GetBoneTransform ( iBIndex , transform ) ;
// find offset of root bone from origin in local space
// Make sure we're detached from hierarchy before doing this!!!
pWeapon - > StopFollowingEntity ( ) ;
pWeapon - > SetAbsOrigin ( Vector ( 0 , 0 , 0 ) ) ;
pWeapon - > SetAbsAngles ( QAngle ( 0 , 0 , 0 ) ) ;
pWeapon - > InvalidateBoneCache ( ) ;
matrix3x4_t rootLocal ;
pWeapon - > GetBoneTransform ( iWeaponBoneIndex , rootLocal ) ;
// invert it
matrix3x4_t rootInvLocal ;
MatrixInvert ( rootLocal , rootInvLocal ) ;
matrix3x4_t weaponMatrix ;
ConcatTransforms ( transform , rootInvLocal , weaponMatrix ) ;
MatrixAngles ( weaponMatrix , angles , origin ) ;
pWeapon - > Teleport ( & origin , & angles , NULL ) ;
}
// Otherwise just set in front of me.
else
{
Vector vFacingDir = BodyDirection2D ( ) ;
vFacingDir = vFacingDir * 10.0 ;
pWeapon - > SetAbsOrigin ( Weapon_ShootPosition ( ) + vFacingDir ) ;
}
}
Vector vecThrow ;
if ( pvecTarget )
{
// I've been told to throw it somewhere specific.
vecThrow = VecCheckToss ( this , pWeapon - > GetAbsOrigin ( ) , * pvecTarget , 0.2 , 1.0 , false ) ;
}
else
{
if ( pVelocity )
{
vecThrow = * pVelocity ;
float flLen = vecThrow . Length ( ) ;
if ( flLen > 400 )
{
VectorNormalize ( vecThrow ) ;
vecThrow * = 400 ;
}
}
else
{
// Nowhere in particular; just drop it.
float throwForce = ( IsPlayer ( ) ) ? 400.0f : random - > RandomInt ( 64 , 128 ) ;
vecThrow = BodyDirection3D ( ) * throwForce ;
}
}
pWeapon - > Drop ( vecThrow ) ;
Weapon_Detach ( pWeapon ) ;
if ( HasSpawnFlags ( SF_NPC_NO_WEAPON_DROP ) )
{
// Don't drop weapons when the super physgun is happening.
UTIL_Remove ( pWeapon ) ;
}
}
//-----------------------------------------------------------------------------
// Lighting origin
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : SetLightingOriginRelative ( CBaseEntity * pLightingOrigin )
{
BaseClass : : SetLightingOriginRelative ( pLightingOrigin ) ;
if ( GetActiveWeapon ( ) )
{
GetActiveWeapon ( ) - > SetLightingOriginRelative ( pLightingOrigin ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Add new weapon to the character
// Input : New weapon
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : Weapon_Equip ( CBaseCombatWeapon * pWeapon )
{
// Add the weapon to my weapon inventory
for ( int i = 0 ; i < MAX_WEAPONS ; i + + )
{
if ( ! m_hMyWeapons [ i ] )
{
m_hMyWeapons . Set ( i , pWeapon ) ;
break ;
}
}
// Weapon is now on my team
pWeapon - > ChangeTeam ( GetTeamNumber ( ) ) ;
// ----------------------
// Give Primary Ammo
// ----------------------
// If gun doesn't use clips, just give ammo
if ( pWeapon - > GetMaxClip1 ( ) = = - 1 )
{
# ifdef HL2_DLL
if ( FStrEq ( STRING ( gpGlobals - > mapname ) , " d3_c17_09 " ) & & FClassnameIs ( pWeapon , " weapon_rpg " ) & & pWeapon - > NameMatches ( " player_spawn_items " ) )
{
// !!!HACK - Don't give any ammo with the spawn equipment RPG in d3_c17_09. This is a chapter
// start and the map is way to easy if you start with 3 RPG rounds. It's fine if a player conserves
// them and uses them here, but it's not OK to start with enough ammo to bypass the snipers completely.
GiveAmmo ( 0 , pWeapon - > m_iPrimaryAmmoType ) ;
}
else
# endif // HL2_DLL
GiveAmmo ( pWeapon - > GetDefaultClip1 ( ) , pWeapon - > m_iPrimaryAmmoType ) ;
}
// If default ammo given is greater than clip
// size, fill clips and give extra ammo
else if ( pWeapon - > GetDefaultClip1 ( ) > pWeapon - > GetMaxClip1 ( ) )
{
pWeapon - > m_iClip1 = pWeapon - > GetMaxClip1 ( ) ;
GiveAmmo ( ( pWeapon - > GetDefaultClip1 ( ) - pWeapon - > GetMaxClip1 ( ) ) , pWeapon - > m_iPrimaryAmmoType ) ;
}
// ----------------------
// Give Secondary Ammo
// ----------------------
// If gun doesn't use clips, just give ammo
if ( pWeapon - > GetMaxClip2 ( ) = = - 1 )
{
GiveAmmo ( pWeapon - > GetDefaultClip2 ( ) , pWeapon - > m_iSecondaryAmmoType ) ;
}
// If default ammo given is greater than clip
// size, fill clips and give extra ammo
else if ( pWeapon - > GetDefaultClip2 ( ) > pWeapon - > GetMaxClip2 ( ) )
{
pWeapon - > m_iClip2 = pWeapon - > GetMaxClip2 ( ) ;
GiveAmmo ( ( pWeapon - > GetDefaultClip2 ( ) - pWeapon - > GetMaxClip2 ( ) ) , pWeapon - > m_iSecondaryAmmoType ) ;
}
pWeapon - > Equip ( this ) ;
// Players don't automatically holster their current weapon
if ( IsPlayer ( ) = = false )
{
if ( m_hActiveWeapon )
{
m_hActiveWeapon - > Holster ( ) ;
// FIXME: isn't this handeled by the weapon?
m_hActiveWeapon - > AddEffects ( EF_NODRAW ) ;
}
SetActiveWeapon ( pWeapon ) ;
m_hActiveWeapon - > RemoveEffects ( EF_NODRAW ) ;
}
// Gotta do this *after* Equip because it may whack maxRange
if ( IsPlayer ( ) = = false )
{
// If SF_NPC_LONG_RANGE spawn flags is set let weapon work from any distance
if ( HasSpawnFlags ( SF_NPC_LONG_RANGE ) )
{
m_hActiveWeapon - > m_fMaxRange1 = 999999999 ;
m_hActiveWeapon - > m_fMaxRange2 = 999999999 ;
}
}
WeaponProficiency_t proficiency ;
proficiency = CalcWeaponProficiency ( pWeapon ) ;
if ( weapon_showproficiency . GetBool ( ) ! = 0 )
{
Msg ( " %s equipped with %s, proficiency is %s \n " , GetClassname ( ) , pWeapon - > GetClassname ( ) , GetWeaponProficiencyName ( proficiency ) ) ;
}
SetCurrentWeaponProficiency ( proficiency ) ;
// Pass the lighting origin over to the weapon if we have one
pWeapon - > SetLightingOriginRelative ( GetLightingOriginRelative ( ) ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Leaves weapon, giving only ammo to the character
// Input : Weapon
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : Weapon_EquipAmmoOnly ( CBaseCombatWeapon * pWeapon )
{
// Check for duplicates
for ( int i = 0 ; i < MAX_WEAPONS ; i + + )
{
if ( m_hMyWeapons [ i ] . Get ( ) & & FClassnameIs ( m_hMyWeapons [ i ] , pWeapon - > GetClassname ( ) ) )
{
// Just give the ammo from the clip
int primaryGiven = ( pWeapon - > UsesClipsForAmmo1 ( ) ) ? pWeapon - > m_iClip1 : pWeapon - > GetPrimaryAmmoCount ( ) ;
int secondaryGiven = ( pWeapon - > UsesClipsForAmmo2 ( ) ) ? pWeapon - > m_iClip2 : pWeapon - > GetSecondaryAmmoCount ( ) ;
int takenPrimary = GiveAmmo ( primaryGiven , pWeapon - > m_iPrimaryAmmoType ) ;
int takenSecondary = GiveAmmo ( secondaryGiven , pWeapon - > m_iSecondaryAmmoType ) ;
if ( pWeapon - > UsesClipsForAmmo1 ( ) )
{
pWeapon - > m_iClip1 - = takenPrimary ;
}
else
{
pWeapon - > SetPrimaryAmmoCount ( pWeapon - > GetPrimaryAmmoCount ( ) - takenPrimary ) ;
}
if ( pWeapon - > UsesClipsForAmmo2 ( ) )
{
pWeapon - > m_iClip2 - = takenSecondary ;
}
else
{
pWeapon - > SetSecondaryAmmoCount ( pWeapon - > GetSecondaryAmmoCount ( ) - takenSecondary ) ;
}
//Only succeed if we've taken ammo from the weapon
if ( takenPrimary > 0 | | takenSecondary > 0 )
return true ;
return false ;
}
}
return false ;
}
//-----------------------------------------------------------------------------
// Purpose: Returns whether the weapon passed in would occupy a slot already occupied by the carrier
// Input : *pWeapon - weapon to test for
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : Weapon_SlotOccupied ( CBaseCombatWeapon * pWeapon )
{
if ( pWeapon = = NULL )
return false ;
//Check to see if there's a resident weapon already in this slot
if ( Weapon_GetSlot ( pWeapon - > GetSlot ( ) ) = = NULL )
return false ;
return true ;
}
//-----------------------------------------------------------------------------
// Purpose: Returns the weapon (if any) in the requested slot
// Input : slot - which slot to poll
//-----------------------------------------------------------------------------
CBaseCombatWeapon * CBaseCombatCharacter : : Weapon_GetSlot ( int slot ) const
{
int targetSlot = slot ;
// Check for that slot being occupied already
for ( int i = 0 ; i < MAX_WEAPONS ; i + + )
{
if ( m_hMyWeapons [ i ] . Get ( ) ! = NULL )
{
// If the slots match, it's already occupied
if ( m_hMyWeapons [ i ] - > GetSlot ( ) = = targetSlot )
return m_hMyWeapons [ i ] ;
}
}
return NULL ;
}
//-----------------------------------------------------------------------------
// Purpose: Get a pointer to a weapon this character has that uses the specified ammo
//-----------------------------------------------------------------------------
CBaseCombatWeapon * CBaseCombatCharacter : : Weapon_GetWpnForAmmo ( int iAmmoIndex )
{
for ( int i = 0 ; i < MAX_WEAPONS ; i + + )
{
CBaseCombatWeapon * weapon = GetWeapon ( i ) ;
if ( ! weapon )
continue ;
if ( weapon - > GetPrimaryAmmoType ( ) = = iAmmoIndex )
return weapon ;
if ( weapon - > GetSecondaryAmmoType ( ) = = iAmmoIndex )
return weapon ;
}
return NULL ;
}
//-----------------------------------------------------------------------------
// Purpose: Can this character operate this weapon?
// Input : A weapon
// Output : true or false
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : Weapon_CanUse ( CBaseCombatWeapon * pWeapon )
{
acttable_t * pTable = pWeapon - > ActivityList ( ) ;
int actCount = pWeapon - > ActivityListCount ( ) ;
if ( actCount < 1 )
{
// If the weapon has no activity table, it definitely cannot be used.
return false ;
}
for ( int i = 0 ; i < actCount ; i + + , pTable + + )
{
if ( pTable - > required )
{
// The NPC might translate the weapon activity into another activity
Activity translatedActivity = NPC_TranslateActivity ( ( Activity ) ( pTable - > weaponAct ) ) ;
if ( SelectWeightedSequence ( translatedActivity ) = = ACTIVITY_NOT_AVAILABLE )
{
return false ;
}
}
}
return true ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
CBaseCombatWeapon * CBaseCombatCharacter : : Weapon_Create ( const char * pWeaponName )
{
CBaseCombatWeapon * pWeapon = static_cast < CBaseCombatWeapon * > ( Create ( pWeaponName , GetLocalOrigin ( ) , GetLocalAngles ( ) , this ) ) ;
return pWeapon ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input :
// Output :
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : Weapon_HandleAnimEvent ( animevent_t * pEvent )
{
// UNDONE: Some check to make sure that pEvent->pSource is a weapon I'm holding?
if ( m_hActiveWeapon )
{
// UNDONE: Pass to pEvent->pSource instead?
m_hActiveWeapon - > Operator_HandleAnimEvent ( pEvent , this ) ;
}
}
void CBaseCombatCharacter : : RemoveAllWeapons ( )
{
ClearActiveWeapon ( ) ;
for ( int i = 0 ; i < MAX_WEAPONS ; i + + )
{
if ( m_hMyWeapons [ i ] )
{
m_hMyWeapons [ i ] - > Delete ( ) ;
m_hMyWeapons . Set ( i , NULL ) ;
}
}
}
// take health
int CBaseCombatCharacter : : TakeHealth ( float flHealth , int bitsDamageType )
{
if ( ! m_takedamage )
return 0 ;
return BaseClass : : TakeHealth ( flHealth , bitsDamageType ) ;
}
/*
= = = = = = = = = = = =
OnTakeDamage
The damage is coming from inflictor , but get mad at attacker
This should be the only function that ever reduces health .
bitsDamageType indicates the type of damage sustained , ie : DMG_SHOCK
Time - based damage : only occurs while the NPC is within the trigger_hurt .
When a NPC is poisoned via an arrow etc it takes all the poison damage at once .
GLOBALS ASSUMED SET : g_iSkillLevel
= = = = = = = = = = = =
*/
int CBaseCombatCharacter : : OnTakeDamage ( const CTakeDamageInfo & info )
{
int retVal = 0 ;
if ( ! m_takedamage )
return 0 ;
m_iDamageCount + + ;
if ( info . GetDamageType ( ) & DMG_SHOCK )
{
g_pEffects - > Sparks ( info . GetDamagePosition ( ) , 2 , 2 ) ;
UTIL_Smoke ( info . GetDamagePosition ( ) , random - > RandomInt ( 10 , 15 ) , 10 ) ;
}
// track damage history
if ( info . GetAttacker ( ) )
{
int attackerTeam = info . GetAttacker ( ) - > GetTeamNumber ( ) ;
m_hasBeenInjured | = ( 1 < < attackerTeam ) ;
for ( int i = 0 ; i < MAX_DAMAGE_TEAMS ; + + i )
{
if ( m_damageHistory [ i ] . team = = attackerTeam )
{
// restart the injury timer
m_damageHistory [ i ] . interval . Start ( ) ;
break ;
}
if ( m_damageHistory [ i ] . team = = TEAM_INVALID )
{
// team not registered yet
m_damageHistory [ i ] . team = attackerTeam ;
m_damageHistory [ i ] . interval . Start ( ) ;
break ;
}
}
}
switch ( m_lifeState )
{
case LIFE_ALIVE :
retVal = OnTakeDamage_Alive ( info ) ;
if ( m_iHealth < = 0 )
{
IPhysicsObject * pPhysics = VPhysicsGetObject ( ) ;
if ( pPhysics )
{
pPhysics - > EnableCollisions ( false ) ;
}
bool bGibbed = false ;
Event_Killed ( info ) ;
// Only classes that specifically request it are gibbed
if ( ShouldGib ( info ) )
{
bGibbed = Event_Gibbed ( info ) ;
}
if ( bGibbed = = false )
{
Event_Dying ( info ) ;
}
}
return retVal ;
break ;
case LIFE_DYING :
return OnTakeDamage_Dying ( info ) ;
default :
case LIFE_DEAD :
retVal = OnTakeDamage_Dead ( info ) ;
if ( m_iHealth < = 0 & & g_pGameRules - > Damage_ShouldGibCorpse ( info . GetDamageType ( ) ) & & ShouldGib ( info ) )
{
Event_Gibbed ( info ) ;
retVal = 0 ;
}
return retVal ;
}
}
int CBaseCombatCharacter : : OnTakeDamage_Alive ( const CTakeDamageInfo & info )
{
// grab the vector of the incoming attack. ( pretend that the inflictor is a little lower than it really is, so the body will tend to fly upward a bit).
Vector vecDir = vec3_origin ;
if ( info . GetInflictor ( ) )
{
vecDir = info . GetInflictor ( ) - > WorldSpaceCenter ( ) - Vector ( 0 , 0 , 10 ) - WorldSpaceCenter ( ) ;
VectorNormalize ( vecDir ) ;
}
g_vecAttackDir = vecDir ;
//!!!LATER - make armor consideration here!
// do the damage
if ( m_takedamage ! = DAMAGE_EVENTS_ONLY )
{
// Separate the fractional amount of damage from the whole
float flFractionalDamage = info . GetDamage ( ) - floor ( info . GetDamage ( ) ) ;
float flIntegerDamage = info . GetDamage ( ) - flFractionalDamage ;
// Add fractional damage to the accumulator
m_flDamageAccumulator + = flFractionalDamage ;
// If the accumulator is holding a full point of damage, move that point
// of damage into the damage we're about to inflict.
if ( m_flDamageAccumulator > = 1.0 )
{
flIntegerDamage + = 1.0 ;
m_flDamageAccumulator - = 1.0 ;
}
if ( flIntegerDamage < = 0 )
return 0 ;
m_iHealth - = flIntegerDamage ;
}
return 1 ;
}
int CBaseCombatCharacter : : OnTakeDamage_Dying ( const CTakeDamageInfo & info )
{
return 1 ;
}
int CBaseCombatCharacter : : OnTakeDamage_Dead ( const CTakeDamageInfo & info )
{
// do the damage
if ( m_takedamage ! = DAMAGE_EVENTS_ONLY )
{
m_iHealth - = info . GetDamage ( ) ;
}
return 1 ;
}
//-----------------------------------------------------------------------------
// Purpose: Sets vBodyDir to the body direction (2D) of the combat character.
// Used as NPC's and players extract facing direction differently
// Input :
// Output :
//-----------------------------------------------------------------------------
QAngle CBaseCombatCharacter : : BodyAngles ( )
{
return GetAbsAngles ( ) ;
}
Vector CBaseCombatCharacter : : BodyDirection2D ( void )
{
Vector vBodyDir = BodyDirection3D ( ) ;
vBodyDir . z = 0 ;
vBodyDir . AsVector2D ( ) . NormalizeInPlace ( ) ;
return vBodyDir ;
}
Vector CBaseCombatCharacter : : BodyDirection3D ( void )
{
QAngle angles = BodyAngles ( ) ;
// FIXME: cache this
Vector vBodyDir ;
AngleVectors ( angles , & vBodyDir ) ;
return vBodyDir ;
}
void CBaseCombatCharacter : : SetTransmit ( CCheckTransmitInfo * pInfo , bool bAlways )
{
// Skip this work if we're already marked for transmission.
if ( pInfo - > m_pTransmitEdict - > Get ( entindex ( ) ) )
return ;
BaseClass : : SetTransmit ( pInfo , bAlways ) ;
bool bLocalPlayer = ( pInfo - > m_pClientEnt = = edict ( ) ) ;
if ( bLocalPlayer )
{
for ( int i = 0 ; i < MAX_WEAPONS ; i + + )
{
CBaseCombatWeapon * pWeapon = m_hMyWeapons [ i ] ;
if ( ! pWeapon )
continue ;
// The local player is sent all of his weapons.
pWeapon - > SetTransmit ( pInfo , bAlways ) ;
}
}
else
{
// The check for EF_NODRAW is useless because the weapon will be networked anyway. In CBaseCombatWeapon::
// UpdateTransmitState all weapons with owners will transmit to clients in the PVS.
if ( m_hActiveWeapon & & ! m_hActiveWeapon - > IsEffectActive ( EF_NODRAW ) )
m_hActiveWeapon - > SetTransmit ( pInfo , bAlways ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Add or Change a class relationship for this entity
// Input :
// Output :
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : AddClassRelationship ( Class_T class_type , Disposition_t disposition , int priority )
{
// First check to see if a relationship has already been declared for this class
// If so, update it with the new relationship
for ( int i = m_Relationship . Count ( ) - 1 ; i > = 0 ; i - - )
{
if ( m_Relationship [ i ] . classType = = class_type )
{
m_Relationship [ i ] . disposition = disposition ;
if ( priority ! = DEF_RELATIONSHIP_PRIORITY )
m_Relationship [ i ] . priority = priority ;
return ;
}
}
int index = m_Relationship . AddToTail ( ) ;
// Add the new class relationship to our relationship table
m_Relationship [ index ] . classType = class_type ;
m_Relationship [ index ] . entity = NULL ;
m_Relationship [ index ] . disposition = disposition ;
m_Relationship [ index ] . priority = ( priority ! = DEF_RELATIONSHIP_PRIORITY ) ? priority : 0 ;
}
//-----------------------------------------------------------------------------
// Purpose: Add or Change a entity relationship for this entity
// Input :
// Output :
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : AddEntityRelationship ( CBaseEntity * pEntity , Disposition_t disposition , int priority )
{
// First check to see if a relationship has already been declared for this entity
// If so, update it with the new relationship
for ( int i = m_Relationship . Count ( ) - 1 ; i > = 0 ; i - - )
{
if ( m_Relationship [ i ] . entity = = pEntity )
{
m_Relationship [ i ] . disposition = disposition ;
if ( priority ! = DEF_RELATIONSHIP_PRIORITY )
m_Relationship [ i ] . priority = priority ;
return ;
}
}
int index = m_Relationship . AddToTail ( ) ;
// Add the new class relationship to our relationship table
m_Relationship [ index ] . classType = CLASS_NONE ;
m_Relationship [ index ] . entity = pEntity ;
m_Relationship [ index ] . disposition = disposition ;
m_Relationship [ index ] . priority = ( priority ! = DEF_RELATIONSHIP_PRIORITY ) ? priority : 0 ;
}
//-----------------------------------------------------------------------------
// Purpose: Removes an entity relationship from our list
// Input : *pEntity - Entity with whom the relationship should be ended
// Output : True is entity was removed, false if it was not found
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : RemoveEntityRelationship ( CBaseEntity * pEntity )
{
// Find the entity in our list, if it exists
for ( int i = m_Relationship . Count ( ) - 1 ; i > = 0 ; i - - )
{
if ( m_Relationship [ i ] . entity = = pEntity )
{
// Done, remove it
m_Relationship . Remove ( i ) ;
return true ;
}
}
return false ;
}
//-----------------------------------------------------------------------------
// Allocates default relationships
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : AllocateDefaultRelationships ( )
{
if ( ! m_DefaultRelationship )
{
m_DefaultRelationship = new Relationship_t * [ NUM_AI_CLASSES ] ;
for ( int i = 0 ; i < NUM_AI_CLASSES ; + + i )
{
// Be default all relationships are neutral of priority zero
m_DefaultRelationship [ i ] = new Relationship_t [ NUM_AI_CLASSES ] ;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Return an interaction ID (so we have no collisions)
// Input :
// Output :
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : SetDefaultRelationship ( Class_T nClass , Class_T nClassTarget , Disposition_t nDisposition , int nPriority )
{
if ( m_DefaultRelationship )
{
m_DefaultRelationship [ nClass ] [ nClassTarget ] . disposition = nDisposition ;
m_DefaultRelationship [ nClass ] [ nClassTarget ] . priority = nPriority ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Fetch the default (ignore ai_relationship changes) relationship
// Input :
// Output :
//-----------------------------------------------------------------------------
Disposition_t CBaseCombatCharacter : : GetDefaultRelationshipDisposition ( Class_T nClassTarget )
{
Assert ( m_DefaultRelationship ! = NULL ) ;
return m_DefaultRelationship [ Classify ( ) ] [ nClassTarget ] . disposition ;
}
//-----------------------------------------------------------------------------
// Purpose: describes the relationship between two types of NPC.
// Input :
// Output :
//-----------------------------------------------------------------------------
Relationship_t * CBaseCombatCharacter : : FindEntityRelationship ( CBaseEntity * pTarget )
{
if ( ! pTarget )
{
static Relationship_t dummy ;
return & dummy ;
}
// First check for specific relationship with this edict
int i ;
for ( i = 0 ; i < m_Relationship . Count ( ) ; i + + )
{
if ( pTarget = = ( CBaseEntity * ) m_Relationship [ i ] . entity )
{
return & m_Relationship [ i ] ;
}
}
if ( pTarget - > Classify ( ) ! = CLASS_NONE )
{
// Then check for relationship with this edict's class
for ( i = 0 ; i < m_Relationship . Count ( ) ; i + + )
{
if ( pTarget - > Classify ( ) = = m_Relationship [ i ] . classType )
{
return & m_Relationship [ i ] ;
}
}
}
AllocateDefaultRelationships ( ) ;
// If none found return the default
return & m_DefaultRelationship [ Classify ( ) ] [ pTarget - > Classify ( ) ] ;
}
Disposition_t CBaseCombatCharacter : : IRelationType ( CBaseEntity * pTarget )
{
if ( pTarget )
return FindEntityRelationship ( pTarget ) - > disposition ;
return D_NU ;
}
//-----------------------------------------------------------------------------
// Purpose: describes the relationship between two types of NPC.
// Input :
// Output :
//-----------------------------------------------------------------------------
int CBaseCombatCharacter : : IRelationPriority ( CBaseEntity * pTarget )
{
if ( pTarget )
return FindEntityRelationship ( pTarget ) - > priority ;
return 0 ;
}
//-----------------------------------------------------------------------------
// Purpose: Get shoot position of BCC at current position/orientation
// Input :
// Output :
//-----------------------------------------------------------------------------
Vector CBaseCombatCharacter : : Weapon_ShootPosition ( )
{
Vector forward , right , up ;
AngleVectors ( GetAbsAngles ( ) , & forward , & right , & up ) ;
Vector vecSrc = GetAbsOrigin ( )
+ forward * m_HackedGunPos . y
+ right * m_HackedGunPos . x
+ up * m_HackedGunPos . z ;
return vecSrc ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
CBaseEntity * CBaseCombatCharacter : : FindHealthItem ( const Vector & vecPosition , const Vector & range )
{
CBaseEntity * list [ 1024 ] ;
int count = UTIL_EntitiesInBox ( list , 1024 , vecPosition - range , vecPosition + range , 0 ) ;
for ( int i = 0 ; i < count ; i + + )
{
CItem * pItem = dynamic_cast < CItem * > ( list [ i ] ) ;
if ( pItem )
{
// Healthkits and healthvials
if ( pItem - > ClassMatches ( " item_health* " ) & & FVisible ( pItem ) )
{
return pItem ;
}
}
}
return NULL ;
}
//-----------------------------------------------------------------------------
// Compares the weapon's center with this character's current origin, so it
// will not give reliable results for weapons that are visible to the NPC
// but are upstairs/downstairs, etc.
//
// A weapon is said to be on the ground if it is no more than 12 inches above
// or below the caller's feet.
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : Weapon_IsOnGround ( CBaseCombatWeapon * pWeapon )
{
if ( pWeapon - > IsConstrained ( ) )
{
// Constrained to a rack.
return false ;
}
if ( fabs ( pWeapon - > WorldSpaceCenter ( ) . z - GetAbsOrigin ( ) . z ) > = 12.0f )
{
return false ;
}
return true ;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &range -
// Output : CBaseEntity
//-----------------------------------------------------------------------------
CBaseEntity * CBaseCombatCharacter : : Weapon_FindUsable ( const Vector & range )
{
bool bConservative = false ;
# ifdef HL2_DLL
if ( hl2_episodic . GetBool ( ) & & ! GetActiveWeapon ( ) )
{
// Unarmed citizens are conservative in their weapon finding
if ( Classify ( ) ! = CLASS_PLAYER_ALLY_VITAL )
{
bConservative = true ;
}
}
# endif
CBaseCombatWeapon * weaponList [ 64 ] ;
CBaseCombatWeapon * pBestWeapon = NULL ;
Vector mins = GetAbsOrigin ( ) - range ;
Vector maxs = GetAbsOrigin ( ) + range ;
int listCount = CBaseCombatWeapon : : GetAvailableWeaponsInBox ( weaponList , ARRAYSIZE ( weaponList ) , mins , maxs ) ;
float fBestDist = 1e6 ;
for ( int i = 0 ; i < listCount ; i + + )
{
// Make sure not moving (ie flying through the air)
Vector velocity ;
CBaseCombatWeapon * pWeapon = weaponList [ i ] ;
Assert ( pWeapon ) ;
pWeapon - > GetVelocity ( & velocity , NULL ) ;
if ( pWeapon - > CanBePickedUpByNPCs ( ) = = false )
continue ;
if ( velocity . LengthSqr ( ) > 1 | | ! Weapon_CanUse ( pWeapon ) )
continue ;
if ( pWeapon - > IsLocked ( this ) )
continue ;
if ( GetActiveWeapon ( ) )
{
// Already armed. Would picking up this weapon improve my situation?
if ( GetActiveWeapon ( ) - > m_iClassname = = pWeapon - > m_iClassname )
{
// No, I'm already using this type of weapon.
continue ;
}
if ( FClassnameIs ( pWeapon , " weapon_pistol " ) )
{
// No, it's a pistol.
continue ;
}
}
float fCurDist = ( pWeapon - > GetLocalOrigin ( ) - GetLocalOrigin ( ) ) . Length ( ) ;
// Give any reserved weapon a bonus
if ( pWeapon - > HasSpawnFlags ( SF_WEAPON_NO_PLAYER_PICKUP ) )
{
fCurDist * = 0.5f ;
}
if ( pBestWeapon )
{
// UNDONE: Better heuristic needed here
// Need to pick by power of weapons
// Don't want to pick a weapon right next to a NPC!
// Give the AR2 a bonus to be selected by making it seem closer.
if ( FClassnameIs ( pWeapon , " weapon_ar2 " ) )
{
fCurDist * = 0.5 ;
}
// choose the last range attack weapon you find or the first available other weapon
if ( ! ( pWeapon - > CapabilitiesGet ( ) & bits_CAP_RANGE_ATTACK_GROUP ) )
{
continue ;
}
else if ( fCurDist > fBestDist )
{
continue ;
}
}
if ( Weapon_IsOnGround ( pWeapon ) )
{
// Weapon appears to be lying on the ground. Make sure this weapon is reachable
// by tracing out a human sized hull just above the weapon. If not, reject
trace_t tr ;
Vector vAboveWeapon = pWeapon - > GetAbsOrigin ( ) ;
UTIL_TraceEntity ( this , vAboveWeapon , vAboveWeapon + Vector ( 0 , 0 , 1 ) , MASK_SOLID , pWeapon , COLLISION_GROUP_NONE , & tr ) ;
if ( tr . startsolid | | ( tr . fraction < 1.0 ) )
continue ;
}
else if ( bConservative )
{
// Skip it.
continue ;
}
if ( FVisible ( pWeapon ) )
{
fBestDist = fCurDist ;
pBestWeapon = pWeapon ;
}
}
if ( pBestWeapon )
{
// Lock this weapon for my exclusive use. Lock it for just a couple of seconds because my AI
// might not actually be able to go pick it up right now.
pBestWeapon - > Lock ( 2.0 , this ) ;
}
return pBestWeapon ;
}
//-----------------------------------------------------------------------------
// Purpose: Give the player some ammo.
// Input : iCount - Amount of ammo to give.
// iAmmoIndex - Index of the ammo into the AmmoInfoArray
// iMax - Max carrying capability of the player
// Output : Amount of ammo actually given
//-----------------------------------------------------------------------------
int CBaseCombatCharacter : : GiveAmmo ( int iCount , int iAmmoIndex , bool bSuppressSound )
{
if ( iCount < = 0 )
return 0 ;
if ( ! g_pGameRules - > CanHaveAmmo ( this , iAmmoIndex ) )
{
// game rules say I can't have any more of this ammo type.
return 0 ;
}
if ( iAmmoIndex < 0 | | iAmmoIndex > = MAX_AMMO_SLOTS )
return 0 ;
int iMax = GetAmmoDef ( ) - > MaxCarry ( iAmmoIndex ) ;
int iAdd = MIN ( iCount , iMax - m_iAmmo [ iAmmoIndex ] ) ;
if ( iAdd < 1 )
return 0 ;
// Ammo pickup sound
if ( ! bSuppressSound )
{
EmitSound ( " BaseCombatCharacter.AmmoPickup " ) ;
}
m_iAmmo . Set ( iAmmoIndex , m_iAmmo [ iAmmoIndex ] + iAdd ) ;
return iAdd ;
}
//-----------------------------------------------------------------------------
// Purpose: Give the player some ammo.
//-----------------------------------------------------------------------------
int CBaseCombatCharacter : : GiveAmmo ( int iCount , const char * szName , bool bSuppressSound )
{
int iAmmoType = GetAmmoDef ( ) - > Index ( szName ) ;
if ( iAmmoType = = - 1 )
{
Msg ( " ERROR: Attempting to give unknown ammo type (%s) \n " , szName ) ;
return 0 ;
}
return GiveAmmo ( iCount , iAmmoType , bSuppressSound ) ;
}
ConVar phys_stressbodyweights ( " phys_stressbodyweights " , " 5.0 " ) ;
void CBaseCombatCharacter : : VPhysicsUpdate ( IPhysicsObject * pPhysics )
{
ApplyStressDamage ( pPhysics , false ) ;
BaseClass : : VPhysicsUpdate ( pPhysics ) ;
}
float CBaseCombatCharacter : : CalculatePhysicsStressDamage ( vphysics_objectstress_t * pStressOut , IPhysicsObject * pPhysics )
{
// stress damage hack.
float mass = pPhysics - > GetMass ( ) ;
CalculateObjectStress ( pPhysics , this , pStressOut ) ;
float stress = ( pStressOut - > receivedStress * m_impactEnergyScale ) / mass ;
// Make sure the stress isn't from being stuck inside some static object.
// how many times your own weight can you hold up?
if ( pStressOut - > hasNonStaticStress & & stress > phys_stressbodyweights . GetFloat ( ) )
{
// if stuck, don't do this!
if ( ! ( pPhysics - > GetGameFlags ( ) & FVPHYSICS_PENETRATING ) )
return 200 ;
}
return 0 ;
}
void CBaseCombatCharacter : : ApplyStressDamage ( IPhysicsObject * pPhysics , bool bRequireLargeObject )
{
# ifdef HL2_DLL
if ( Classify ( ) = = CLASS_PLAYER_ALLY | | Classify ( ) = = CLASS_PLAYER_ALLY_VITAL )
{
// Bypass stress completely for allies and vitals.
if ( hl2_episodic . GetBool ( ) )
return ;
}
# endif //HL2_DLL
vphysics_objectstress_t stressOut ;
float damage = CalculatePhysicsStressDamage ( & stressOut , pPhysics ) ;
if ( damage > 0 )
{
if ( bRequireLargeObject & & ! stressOut . hasLargeObjectContact )
return ;
//Msg("Stress! %.2f / %.2f\n", stressOut.exertedStress, stressOut.receivedStress );
CTakeDamageInfo dmgInfo ( GetWorldEntity ( ) , GetWorldEntity ( ) , vec3_origin , vec3_origin , damage , DMG_CRUSH ) ;
dmgInfo . SetDamageForce ( Vector ( 0 , 0 , - stressOut . receivedStress * GetCurrentGravity ( ) * gpGlobals - > frametime ) ) ;
dmgInfo . SetDamagePosition ( GetAbsOrigin ( ) ) ;
TakeDamage ( dmgInfo ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : const impactdamagetable_t
//-----------------------------------------------------------------------------
const impactdamagetable_t & CBaseCombatCharacter : : GetPhysicsImpactDamageTable ( void )
{
return gDefaultNPCImpactDamageTable ;
}
// how much to amplify impact forces
// This is to account for the ragdolls responding differently than
// the shadow objects. Also this makes the impacts more dramatic.
ConVar phys_impactforcescale ( " phys_impactforcescale " , " 1.0 " ) ;
ConVar phys_upimpactforcescale ( " phys_upimpactforcescale " , " 0.375 " ) ;
void CBaseCombatCharacter : : VPhysicsShadowCollision ( int index , gamevcollisionevent_t * pEvent )
{
int otherIndex = ! index ;
CBaseEntity * pOther = pEvent - > pEntities [ otherIndex ] ;
IPhysicsObject * pOtherPhysics = pEvent - > pObjects [ otherIndex ] ;
if ( ! pOther )
return ;
// Ragdolls are marked as dying.
if ( pOther - > m_lifeState = = LIFE_DYING )
return ;
if ( pOther - > GetMoveType ( ) ! = MOVETYPE_VPHYSICS )
return ;
if ( ! pOtherPhysics - > IsMoveable ( ) )
return ;
if ( pOther = = GetGroundEntity ( ) )
return ;
// Player can't damage himself if he's was physics attacker *on this frame*
// which can occur owing to ordering issues it appears.
float flOtherAttackerTime = 0.0f ;
# if defined( HL2_DLL ) && !defined( HL2MP )
if ( HL2GameRules ( ) - > MegaPhyscannonActive ( ) = = true )
{
flOtherAttackerTime = 1.0f ;
}
# endif // HL2_DLL && !HL2MP
if ( this = = pOther - > HasPhysicsAttacker ( flOtherAttackerTime ) )
return ;
int damageType = 0 ;
float damage = 0 ;
damage = CalculatePhysicsImpactDamage ( index , pEvent , GetPhysicsImpactDamageTable ( ) , m_impactEnergyScale , false , damageType ) ;
if ( damage < = 0 )
return ;
// NOTE: We really need some rotational motion for some of these collisions.
// REVISIT: Maybe resolve this collision on death with a different (not approximately infinite like AABB tensor)
// inertia tensor to get torque?
Vector damageForce = pEvent - > postVelocity [ index ] * pEvent - > pObjects [ index ] - > GetMass ( ) * phys_impactforcescale . GetFloat ( ) ;
IServerVehicle * vehicleOther = pOther - > GetServerVehicle ( ) ;
if ( vehicleOther )
{
CBaseCombatCharacter * pPassenger = vehicleOther - > GetPassenger ( ) ;
if ( pPassenger ! = NULL )
{
// flag as vehicle damage
damageType | = DMG_VEHICLE ;
// if hit by vehicle driven by player, add some upward velocity to force
float len = damageForce . Length ( ) ;
damageForce . z + = len * phys_upimpactforcescale . GetFloat ( ) ;
//Msg("Force %.1f / %.1f\n", damageForce.Length(), damageForce.z );
if ( pPassenger - > IsPlayer ( ) )
{
CBasePlayer * pPlayer = assert_cast < CBasePlayer * > ( pPassenger ) ;
if ( damage > = GetMaxHealth ( ) )
{
pPlayer - > RumbleEffect ( RUMBLE_357 , 0 , RUMBLE_FLAG_RESTART ) ;
}
else
{
pPlayer - > RumbleEffect ( RUMBLE_PISTOL , 0 , RUMBLE_FLAG_RESTART ) ;
}
}
}
}
Vector damagePos ;
pEvent - > pInternalData - > GetContactPoint ( damagePos ) ;
CTakeDamageInfo dmgInfo ( pOther , pOther , damageForce , damagePos , damage , damageType ) ;
// FIXME: is there a better way for physics objects to keep track of what root entity responsible for them moving?
CBasePlayer * pPlayer = pOther - > HasPhysicsAttacker ( 1.0 ) ;
if ( pPlayer )
{
dmgInfo . SetAttacker ( pPlayer ) ;
}
// UNDONE: Find one near damagePos?
m_nForceBone = 0 ;
PhysCallbackDamage ( this , dmgInfo , * pEvent , index ) ;
}
//-----------------------------------------------------------------------------
// Purpose: this entity is exploding, or otherwise needs to inflict damage upon
// entities within a certain range. only damage ents that can clearly
// be seen by the explosion!
// Input :
// Output :
//-----------------------------------------------------------------------------
void RadiusDamage ( const CTakeDamageInfo & info , const Vector & vecSrc , float flRadius , int iClassIgnore , CBaseEntity * pEntityIgnore )
{
// NOTE: I did this this way so I wouldn't have to change a whole bunch of
// code unnecessarily. We need TF2 specific rules for RadiusDamage, so I moved
// the implementation of radius damage into gamerules. All existing code calls
// this method, which calls the game rules method
g_pGameRules - > RadiusDamage ( info , vecSrc , flRadius , iClassIgnore , pEntityIgnore ) ;
// Let the world know if this was an explosion.
if ( info . GetDamageType ( ) & DMG_BLAST )
{
// Even the tiniest explosion gets attention. Don't let the radius
// be less than 128 units.
float soundRadius = MAX ( 128.0f , flRadius * 1.5 ) ;
CSoundEnt : : InsertSound ( SOUND_COMBAT | SOUND_CONTEXT_EXPLOSION , vecSrc , soundRadius , 0.25 , info . GetInflictor ( ) ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: Change active weapon and notify derived classes
//
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : SetActiveWeapon ( CBaseCombatWeapon * pNewWeapon )
{
CBaseCombatWeapon * pOldWeapon = m_hActiveWeapon ;
if ( pNewWeapon ! = pOldWeapon )
{
m_hActiveWeapon = pNewWeapon ;
OnChangeActiveWeapon ( pOldWeapon , pNewWeapon ) ;
}
}
//-----------------------------------------------------------------------------
// Consider the weapon's built-in accuracy, this character's proficiency with
// the weapon, and the status of the target. Use this information to determine
// how accurately to shoot at the target.
//-----------------------------------------------------------------------------
Vector CBaseCombatCharacter : : GetAttackSpread ( CBaseCombatWeapon * pWeapon , CBaseEntity * pTarget )
{
if ( pWeapon )
return pWeapon - > GetBulletSpread ( GetCurrentWeaponProficiency ( ) ) ;
return VECTOR_CONE_15DEGREES ;
}
//-----------------------------------------------------------------------------
float CBaseCombatCharacter : : GetSpreadBias ( CBaseCombatWeapon * pWeapon , CBaseEntity * pTarget )
{
if ( pWeapon )
return pWeapon - > GetSpreadBias ( GetCurrentWeaponProficiency ( ) ) ;
return 1.0 ;
}
# ifdef GLOWS_ENABLE
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : AddGlowEffect ( void )
{
SetTransmitState ( FL_EDICT_ALWAYS ) ;
m_bGlowEnabled . Set ( true ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : RemoveGlowEffect ( void )
{
m_bGlowEnabled . Set ( false ) ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : IsGlowEffectActive ( void )
{
return m_bGlowEnabled ;
}
# endif // GLOWS_ENABLE
//-----------------------------------------------------------------------------
// Assume everyone is average with every weapon. Override this to make exceptions.
//-----------------------------------------------------------------------------
WeaponProficiency_t CBaseCombatCharacter : : CalcWeaponProficiency ( CBaseCombatWeapon * pWeapon )
{
return WEAPON_PROFICIENCY_AVERAGE ;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
# define MAX_MISS_CANDIDATES 16
CBaseEntity * CBaseCombatCharacter : : FindMissTarget ( void )
{
CBaseEntity * pMissCandidates [ MAX_MISS_CANDIDATES ] ;
int numMissCandidates = 0 ;
CBasePlayer * pPlayer = UTIL_GetLocalPlayer ( ) ;
CBaseEntity * pEnts [ 256 ] ;
Vector radius ( 100 , 100 , 100 ) ;
Vector vecSource = GetAbsOrigin ( ) ;
int numEnts = UTIL_EntitiesInBox ( pEnts , 256 , vecSource - radius , vecSource + radius , 0 ) ;
for ( int i = 0 ; i < numEnts ; i + + )
{
if ( pEnts [ i ] = = NULL )
continue ;
// New rule for this system. Don't shoot what the player won't see.
if ( pPlayer & & ! pPlayer - > FInViewCone ( pEnts [ i ] ) )
continue ;
if ( numMissCandidates > = MAX_MISS_CANDIDATES )
break ;
//See if it's a good target candidate
if ( FClassnameIs ( pEnts [ i ] , " prop_dynamic " ) | |
FClassnameIs ( pEnts [ i ] , " prop_physics " ) | |
FClassnameIs ( pEnts [ i ] , " physics_prop " ) )
{
pMissCandidates [ numMissCandidates + + ] = pEnts [ i ] ;
continue ;
}
}
if ( numMissCandidates = = 0 )
return NULL ;
return pMissCandidates [ random - > RandomInt ( 0 , numMissCandidates - 1 ) ] ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : ShouldShootMissTarget ( CBaseCombatCharacter * pAttacker )
{
// Don't shoot at NPC's right now.
return false ;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : InputKilledNPC ( inputdata_t & inputdata )
{
OnKilledNPC ( inputdata . pActivator ? inputdata . pActivator - > MyCombatCharacterPointer ( ) : NULL ) ;
}
//-----------------------------------------------------------------------------
// Purpose: Overload our muzzle flash and send it to any actively held weapon
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : DoMuzzleFlash ( )
{
// Our weapon takes our muzzle flash command
CBaseCombatWeapon * pWeapon = GetActiveWeapon ( ) ;
if ( pWeapon )
{
pWeapon - > DoMuzzleFlash ( ) ;
//NOTENOTE: We do not chain to the base here
}
else
{
BaseClass : : DoMuzzleFlash ( ) ;
}
}
//-----------------------------------------------------------------------------
// Purpose: return true if given target cant be seen because of fog
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : IsHiddenByFog ( const Vector & target ) const
{
float range = EyePosition ( ) . DistTo ( target ) ;
return IsHiddenByFog ( range ) ;
}
//-----------------------------------------------------------------------------
// Purpose: return true if given target cant be seen because of fog
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : IsHiddenByFog ( CBaseEntity * target ) const
{
if ( ! target )
return false ;
float range = EyePosition ( ) . DistTo ( target - > WorldSpaceCenter ( ) ) ;
return IsHiddenByFog ( range ) ;
}
//-----------------------------------------------------------------------------
// Purpose: return true if given target cant be seen because of fog
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : IsHiddenByFog ( float range ) const
{
if ( GetFogObscuredRatio ( range ) > = 1.0f )
return true ;
return false ;
}
//-----------------------------------------------------------------------------
// Purpose: return 0-1 ratio where zero is not obscured, and 1 is completely obscured
//-----------------------------------------------------------------------------
float CBaseCombatCharacter : : GetFogObscuredRatio ( const Vector & target ) const
{
float range = EyePosition ( ) . DistTo ( target ) ;
return GetFogObscuredRatio ( range ) ;
}
//-----------------------------------------------------------------------------
// Purpose: return 0-1 ratio where zero is not obscured, and 1 is completely obscured
//-----------------------------------------------------------------------------
float CBaseCombatCharacter : : GetFogObscuredRatio ( CBaseEntity * target ) const
{
if ( ! target )
return false ;
float range = EyePosition ( ) . DistTo ( target - > WorldSpaceCenter ( ) ) ;
return GetFogObscuredRatio ( range ) ;
}
//-----------------------------------------------------------------------------
// Purpose: return 0-1 ratio where zero is not obscured, and 1 is completely obscured
//-----------------------------------------------------------------------------
float CBaseCombatCharacter : : GetFogObscuredRatio ( float range ) const
{
/* TODO: Get global fog from map somehow since nav mesh fog is gone
fogparams_t fog ;
GetFogParams ( & fog ) ;
if ( ! fog . enable )
return 0.0f ;
if ( range < = fog . start )
return 0.0f ;
if ( range > = fog . end )
return 1.0f ;
float ratio = ( range - fog . start ) / ( fog . end - fog . start ) ;
ratio = MIN ( ratio , fog . maxdensity ) ;
return ratio ;
*/
return 0.0f ;
}
//-----------------------------------------------------------------------------
// Purpose: Invoke this to update our last known nav area
// (since there is no think method chained to CBaseCombatCharacter)
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : UpdateLastKnownArea ( void )
{
# ifdef NEXT_BOT
if ( TheNavMesh - > IsGenerating ( ) )
{
ClearLastKnownArea ( ) ;
return ;
}
if ( nb_last_area_update_tolerance . GetFloat ( ) > 0.0f )
{
// skip this test if we're not standing on the world (ie: elevators that move us)
if ( GetGroundEntity ( ) = = NULL | | GetGroundEntity ( ) - > IsWorld ( ) )
{
if ( m_lastNavArea & & m_NavAreaUpdateMonitor . IsMarkSet ( ) & & ! m_NavAreaUpdateMonitor . TargetMoved ( this ) )
return ;
m_NavAreaUpdateMonitor . SetMark ( this , nb_last_area_update_tolerance . GetFloat ( ) ) ;
}
}
// find the area we are directly standing in
CNavArea * area = TheNavMesh - > GetNearestNavArea ( this , GETNAVAREA_CHECK_GROUND | GETNAVAREA_CHECK_LOS , 50.0f ) ;
if ( ! area )
return ;
// make sure we can actually use this area - if not, consider ourselves off the mesh
if ( ! IsAreaTraversable ( area ) )
return ;
if ( area ! = m_lastNavArea )
{
// player entered a new nav area
if ( m_lastNavArea )
{
m_lastNavArea - > DecrementPlayerCount ( m_registeredNavTeam , entindex ( ) ) ;
m_lastNavArea - > OnExit ( this , area ) ;
}
m_registeredNavTeam = GetTeamNumber ( ) ;
area - > IncrementPlayerCount ( m_registeredNavTeam , entindex ( ) ) ;
area - > OnEnter ( this , m_lastNavArea ) ;
OnNavAreaChanged ( area , m_lastNavArea ) ;
m_lastNavArea = area ;
}
# endif
}
//-----------------------------------------------------------------------------
// Purpose: Return true if we can use (walk through) the given area
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : IsAreaTraversable ( const CNavArea * area ) const
{
# ifdef NEXT_BOT
return area ? ! area - > IsBlocked ( GetTeamNumber ( ) ) : false ;
# endif
return false ;
}
//-----------------------------------------------------------------------------
// Purpose: Leaving the nav mesh
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : ClearLastKnownArea ( void )
{
# ifdef NEXT_BOT
OnNavAreaChanged ( NULL , m_lastNavArea ) ;
if ( m_lastNavArea )
{
m_lastNavArea - > DecrementPlayerCount ( m_registeredNavTeam , entindex ( ) ) ;
m_lastNavArea - > OnExit ( this , NULL ) ;
m_lastNavArea = NULL ;
m_registeredNavTeam = TEAM_INVALID ;
}
# endif
}
//-----------------------------------------------------------------------------
// Purpose: Handling editor removing the area we're standing upon
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : OnNavAreaRemoved ( CNavArea * removedArea )
{
# ifdef NEXT_BOT
if ( m_lastNavArea = = removedArea )
{
ClearLastKnownArea ( ) ;
}
# endif
}
//-----------------------------------------------------------------------------
// Purpose: Changing team, maintain associated data
//-----------------------------------------------------------------------------
void CBaseCombatCharacter : : ChangeTeam ( int iTeamNum )
{
// old team member no longer in the nav mesh
ClearLastKnownArea ( ) ;
# ifdef GLOWS_ENABLE
RemoveGlowEffect ( ) ;
# endif // GLOWS_ENABLE
BaseClass : : ChangeTeam ( iTeamNum ) ;
}
//-----------------------------------------------------------------------------
// Return true if we have ever been injured by a member of the given team
//-----------------------------------------------------------------------------
bool CBaseCombatCharacter : : HasEverBeenInjured ( int team /*= TEAM_ANY */ ) const
{
if ( team = = TEAM_ANY )
{
return ( m_hasBeenInjured = = 0 ) ? false : true ;
}
int teamMask = 1 < < team ;
if ( m_hasBeenInjured & teamMask )
{
return true ;
}
return false ;
}
//-----------------------------------------------------------------------------
// Return time since we were hurt by a member of the given team
//-----------------------------------------------------------------------------
float CBaseCombatCharacter : : GetTimeSinceLastInjury ( int team /*= TEAM_ANY */ ) const
{
const float never = 999999999999.9f ;
if ( team = = TEAM_ANY )
{
float time = never ;
// find most recent injury time
for ( int i = 0 ; i < MAX_DAMAGE_TEAMS ; + + i )
{
if ( m_damageHistory [ i ] . team ! = TEAM_INVALID )
{
if ( m_damageHistory [ i ] . interval . GetElapsedTime ( ) < time )
{
time = m_damageHistory [ i ] . interval . GetElapsedTime ( ) ;
}
}
}
return time ;
}
else
{
for ( int i = 0 ; i < MAX_DAMAGE_TEAMS ; + + i )
{
if ( m_damageHistory [ i ] . team = = team )
{
return m_damageHistory [ i ] . interval . GetElapsedTime ( ) ;
}
}
}
return never ;
}