You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3599 lines
102 KiB
3599 lines
102 KiB
//========= 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; |
|
} |
|
|
|
|