aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/basecombatcharacter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mp/src/game/server/basecombatcharacter.cpp')
-rw-r--r--mp/src/game/server/basecombatcharacter.cpp7184
1 files changed, 3592 insertions, 3592 deletions
diff --git a/mp/src/game/server/basecombatcharacter.cpp b/mp/src/game/server/basecombatcharacter.cpp
index 7fb5cce1..9f6a8674 100644
--- a/mp/src/game/server/basecombatcharacter.cpp
+++ b/mp/src/game/server/basecombatcharacter.cpp
@@ -1,3592 +1,3592 @@
-//========= 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
- m_lastNavArea = NULL;
- 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
-{
- return area ? !area->IsBlocked( GetTeamNumber() ) : false;
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Leaving the nav mesh
-//-----------------------------------------------------------------------------
-void CBaseCombatCharacter::ClearLastKnownArea( void )
-{
- 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;
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// Purpose: Handling editor removing the area we're standing upon
-//-----------------------------------------------------------------------------
-void CBaseCombatCharacter::OnNavAreaRemoved( CNavArea *removedArea )
-{
- if ( m_lastNavArea == removedArea )
- {
- ClearLastKnownArea();
- }
-}
-
-
-//-----------------------------------------------------------------------------
-// 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;
-}
-
+//========= 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
+ m_lastNavArea = NULL;
+ 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
+{
+ return area ? !area->IsBlocked( GetTeamNumber() ) : false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Leaving the nav mesh
+//-----------------------------------------------------------------------------
+void CBaseCombatCharacter::ClearLastKnownArea( void )
+{
+ 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;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handling editor removing the area we're standing upon
+//-----------------------------------------------------------------------------
+void CBaseCombatCharacter::OnNavAreaRemoved( CNavArea *removedArea )
+{
+ if ( m_lastNavArea == removedArea )
+ {
+ ClearLastKnownArea();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// 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;
+}
+