From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/basecombatcharacter.cpp | 7184 ++++++++++++++-------------- 1 file changed, 3592 insertions(+), 3592 deletions(-) (limited to 'mp/src/game/server/basecombatcharacter.cpp') 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; iClearAllRecipients(); - - 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 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 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( 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= 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;iDeathNotice( 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( 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; iOBBSize().x * CollisionProp()->OBBSize().x + - CollisionProp()->OBBSize().y * CollisionProp()->OBBSize().y ); - - CBaseCombatWeapon *pActiveWeapon = GetActiveWeapon(); - for (int i=0; iRemoveSolidFlags( 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;iChangeTeam( 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;iGetClassname()) ) - { - // 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( 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; iEnableCollisions( 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; iClassify() != CLASS_NONE) - { - // Then check for relationship with this edict's class - for (i=0;iClassify() == 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(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(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; iClearAllRecipients(); + + 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 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 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( 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= 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;iDeathNotice( 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( 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; iOBBSize().x * CollisionProp()->OBBSize().x + + CollisionProp()->OBBSize().y * CollisionProp()->OBBSize().y ); + + CBaseCombatWeapon *pActiveWeapon = GetActiveWeapon(); + for (int i=0; iRemoveSolidFlags( 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;iChangeTeam( 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;iGetClassname()) ) + { + // 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( 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; iEnableCollisions( 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; iClassify() != CLASS_NONE) + { + // Then check for relationship with this edict's class + for (i=0;iClassify() == 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(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(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