4485 lines
175 KiB
C++
4485 lines
175 KiB
C++
![]() |
//
|
||
|
// weapons/weapondamage.cpp
|
||
|
//
|
||
|
// Copyright (C) 1999-2010 Rockstar Games. All Rights Reserved.
|
||
|
//
|
||
|
|
||
|
// File header
|
||
|
#include "Weapons/WeaponDamage.h"
|
||
|
|
||
|
// Rage headers
|
||
|
#include "phglass/glassinstance.h"
|
||
|
|
||
|
// Game headers
|
||
|
#include "audio/northaudioengine.h"
|
||
|
#include "animation/MovePed.h"
|
||
|
#include "animation/FacialData.h"
|
||
|
#include "control/trafficlights.h"
|
||
|
#include "control/replay/replay.h"
|
||
|
#include "control/replay/Misc/GlassPackets.h"
|
||
|
#include "Event/EventDamage.h"
|
||
|
#include "Event/EventShocking.h"
|
||
|
#include "Event/EventWeapon.h"
|
||
|
#include "Event/ShockingEvents.h"
|
||
|
#include "modelinfo/PedModelInfo.h"
|
||
|
#include "network/Objects/Entities/NetObjPlayer.h"
|
||
|
#include "Objects/Door.h"
|
||
|
#include "Objects/DoorTuning.h"
|
||
|
#include "Peds/PedCapsule.h"
|
||
|
#include "Peds/PedIntelligence.h"
|
||
|
#include "Peds/Ped.h"
|
||
|
#include "Peds/rendering/PedDamage.h"
|
||
|
#include "Physics/GTAInst.h"
|
||
|
#include "Physics/Physics.h"
|
||
|
#include "Pickups/Pickup.h"
|
||
|
#include "Pickups/PickupManager.h"
|
||
|
#include "Renderer/PostProcessFX.h"
|
||
|
#include "scene/playerswitch/PlayerSwitchInterface.h"
|
||
|
#include "Stats/StatsInterface.h"
|
||
|
#include "Stats/StatsMgr.h"
|
||
|
#include "Stats/StatsUtils.h"
|
||
|
#include "Task/Animation/TaskScriptedAnimation.h"
|
||
|
#include "Task/Combat/CombatMeleeDebug.h"
|
||
|
#include "Task/Combat/TaskAnimatedHitByExplosion.h"
|
||
|
#include "Task/Combat/TaskCombatMelee.h"
|
||
|
#include "Task/Combat/TaskDamageDeath.h"
|
||
|
#include "Task/Default/TaskIncapacitated.h"
|
||
|
#include "Task/Movement/Jumping/TaskFallGetUp.h"
|
||
|
#include "Task/Physics/TaskNM.h"
|
||
|
#include "Task/Physics/TaskNMBalance.h"
|
||
|
#include "Task/Physics/TaskNMElectrocute.h"
|
||
|
#include "Task/Physics/TaskNMExplosion.h"
|
||
|
#include "Task/Physics/TaskNMFallDown.h"
|
||
|
#include "Task/Physics/TaskNMFlinch.h"
|
||
|
#include "Task/Physics/TaskNMHighFall.h"
|
||
|
#include "Task/Physics/TaskNMOnFire.h"
|
||
|
#include "Task/Physics/TaskNMRelax.h"
|
||
|
#include "Task/Physics/TaskNMShot.h"
|
||
|
#include "Task/Physics/TaskNMSimple.h"
|
||
|
#include "Task/Scenario/Info/ScenarioInfo.h"
|
||
|
#include "Task/Scenario/Types/TaskUseScenario.h"
|
||
|
#include "Task/Vehicle/TaskEnterVehicle.h"
|
||
|
#include "Task/Vehicle/TaskInVehicle.h"
|
||
|
#include "task/Vehicle/TaskMountAnimalWeapon.h"
|
||
|
#include "Vfx/Decals/DecalManager.h"
|
||
|
#include "Vfx/Misc/Fire.h"
|
||
|
#include "Vfx/Misc/LODLightManager.h"
|
||
|
#include "Vfx/Systems/VfxBlood.h"
|
||
|
#include "Vfx/Systems/VfxWeapon.h"
|
||
|
#include "vfx/vehicleglass/VehicleGlassManager.h"
|
||
|
#include "Vfx/VfxHelper.h"
|
||
|
#include "vehicles/heli.h"
|
||
|
#include "Vehicles/Bike.h"
|
||
|
#include "vehicles/Boat.h"
|
||
|
#include "vehicles/Trailer.h"
|
||
|
#include "Weapons/Projectiles/Projectile.h"
|
||
|
#include "Weapons/Weapon.h"
|
||
|
#include "fragment/cache.h"
|
||
|
#include "glassPaneSyncing/GlassPaneManager.h"
|
||
|
#include "Script/Script.h"
|
||
|
|
||
|
#if __ASSERT
|
||
|
#include "Stats/StatsTypes.h"
|
||
|
#endif
|
||
|
|
||
|
XPARAM(invincibleMigratingPeds);
|
||
|
|
||
|
// Macro to disable optimisations if set
|
||
|
WEAPON_OPTIMISATIONS()
|
||
|
NETWORK_OPTIMISATIONS()
|
||
|
AI_VEHICLE_OPTIMISATIONS()
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// Constants
|
||
|
static dev_float MELEE_WEAPON_FORCE_MULT = 10.0f;
|
||
|
static bank_float GUN_WEAPON_CAR_DOOR_MOD = 0.066f;
|
||
|
static bank_float GUN_WEAPON_CAR_TURRET_MOD = 0.5f;
|
||
|
|
||
|
static dev_bool NM_APPLIES_SHOT_IMPULSE = true; // Also need to comment out ApplyBulletImpulse message in TaskNMShot to disable
|
||
|
|
||
|
const float g_ExplosiveAmmoShakeAmplitude = 0.1f;
|
||
|
const float g_ExplosiveMeleeShakeAmplitude = 0.3f;
|
||
|
|
||
|
const float CWeaponDamage::MELEE_VEHICLE_DAMAGE_MODIFIER = 0.75f;
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
f32 CWeaponDamage::DoWeaponImpact(CWeapon* pWeapon, CEntity* pFiringEntity, const Vector3& vWeaponPos, WorldProbe::CShapeTestResults& refResults, const f32 fApplyDamage, const fwFlags32& flags, const bool bFireDriveby /* = false */, const bool bTemporaryNetworkWeapon /* = false */, const sDamageModifiers* pModifiers /* = NULL */, const sMeleeInfo *pMeleeInfo /*= NULL*/, bool bulletUnderWater /* = false */, NetworkWeaponImpactInfo * const networkWeaponImpactInfo /* = NULL */, const float fRecoilAccuracyWhenFired /* = 1.f */, const float fFallOffRangeModifier /* = 1.f*/, const float fFallOffDamageModifier /* = 1.f*/, bool bReceiveFireMessage, bool bShouldGenerateVFX, Vector3* hitDirOut /* = 0 */, const Vector3* hitDirIn /* = 0 */, u8 damageAggregationCount /*= 0*/)
|
||
|
{
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpact fc[%u] pWeapon[%p] pFiringEntity[%p] vWeaponPos[%f %f %f] fApplyDamage[%f] bReceiveFireMessage[%d]",fwTimer::GetFrameCount(),pWeapon,pFiringEntity,vWeaponPos.x,vWeaponPos.y,vWeaponPos.z,fApplyDamage,bReceiveFireMessage);
|
||
|
|
||
|
phMaterialMgr::Id lastMaterialForAudio = ~0U;
|
||
|
u32 uNumImpactsForAudio = 0;
|
||
|
|
||
|
f32 fDamageDone = 0.0f;
|
||
|
f32 fDamageMod = 1.0f;
|
||
|
|
||
|
CPed* pFiringPed = (pFiringEntity && pFiringEntity->GetIsTypePed()) ? static_cast<CPed*>(pFiringEntity) : NULL;
|
||
|
if(!pFiringPed && pFiringEntity && pFiringEntity->GetIsTypeVehicle())
|
||
|
{
|
||
|
pFiringPed = static_cast<CVehicle*>(pFiringEntity)->GetDriver();
|
||
|
}
|
||
|
|
||
|
const CWeaponInfo* pWeaponInfo = pWeapon ? pWeapon->GetWeaponInfo() : NULL;
|
||
|
if (pFiringPed && pFiringPed->IsPlayer() && pWeaponInfo)
|
||
|
{
|
||
|
const CPedModelInfo* mi = pFiringPed->GetPedModelInfo();
|
||
|
Assert(mi);
|
||
|
const CPedModelInfo::PersonalityData& pd = mi->GetPersonalitySettings();
|
||
|
|
||
|
const u32 WeaponGroup = pWeaponInfo->GetGroup();
|
||
|
if (WeaponGroup == WEAPONGROUP_PISTOL)
|
||
|
fDamageMod = pd.GetHandGunDmgMod();
|
||
|
else if (WeaponGroup == WEAPONGROUP_RIFLE)
|
||
|
fDamageMod = pd.GetRifleDmgMod();
|
||
|
else if (WeaponGroup == WEAPONGROUP_SMG)
|
||
|
fDamageMod = pd.GetSmgDamageMod();
|
||
|
}
|
||
|
|
||
|
bool isUnderWater = bulletUnderWater;
|
||
|
// Components may become invalid if the instance changes LOD during iteration
|
||
|
// Store the high LOD component of instances prior to iterating below and then map to the current LOD during iteration
|
||
|
for(WorldProbe::ResultIterator it = refResults.begin(); it < refResults.last_result(); ++it)
|
||
|
{
|
||
|
// Instances may get deleted because of ApplyImpact calls earlier in the loop
|
||
|
// so we need to check they're still valid.
|
||
|
if(!it->GetHitDetected() || !it->GetHitInst())
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(it->GetHitInst()->GetClassType() == PH_INST_FRAG_PED)
|
||
|
{
|
||
|
int highLODComponent = static_cast<fragInstNMGta*>(it->GetHitInst())->MapRagdollLODComponentCurrentToHigh(it->GetHitComponent());
|
||
|
if(highLODComponent >= 0)
|
||
|
{
|
||
|
it->SetHitComponent(static_cast<u16>(highLODComponent));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for(WorldProbe::ResultIterator it = refResults.begin(); it < refResults.last_result(); ++it)
|
||
|
{
|
||
|
// Instances may get deleted because of ApplyImpact calls earlier in the loop
|
||
|
// so we need to check they're still valid.
|
||
|
if(!it->GetHitDetected() || !it->GetHitInst())
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if(it->GetHitInst()->GetClassType() == PH_INST_FRAG_PED)
|
||
|
{
|
||
|
int currentLODComponent = static_cast<fragInstNMGta*>(it->GetHitInst())->MapRagdollLODComponentHighToCurrent(it->GetHitComponent());
|
||
|
if(currentLODComponent >= 0)
|
||
|
{
|
||
|
it->SetHitComponent(static_cast<u16>(currentLODComponent));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Store the time position and time stamp of the first intersection
|
||
|
if(it == refResults.begin())
|
||
|
{
|
||
|
if(pFiringPed && pFiringPed->GetWeaponManager())
|
||
|
{
|
||
|
pFiringPed->GetWeaponManager()->SetLastWeaponImpactPos(it->GetHitPosition());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if __BANK
|
||
|
if(CExplosionManager::ms_debugWeaponExplosions && (!pFiringEntity || (pFiringEntity->GetIsTypePed() && static_cast<CPed*>(pFiringEntity)->IsLocalPlayer())))
|
||
|
{
|
||
|
CEntity* pHitEntity = CPhysics::GetEntityFromInst(it->GetHitInst());
|
||
|
|
||
|
CExplosionManager::DebugCreateExplosion(pFiringEntity, it->GetHitPosition() + 0.1f * it->GetHitNormal(), it->GetHitNormal(), pHitEntity);
|
||
|
}
|
||
|
#endif // __BANK
|
||
|
|
||
|
bool bLocalFiringEntity = true;
|
||
|
if ( NetworkInterface::IsGameInProgress() && pFiringEntity )
|
||
|
{
|
||
|
netObject* pNetObject = NetworkUtils::GetNetworkObjectFromEntity(pFiringEntity);
|
||
|
if (pNetObject && pNetObject->IsClone())
|
||
|
{
|
||
|
bLocalFiringEntity = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpact--pWeaponInfo[%p] bLocalFiringEntity[%d]",pWeaponInfo,bLocalFiringEntity);
|
||
|
|
||
|
// Get the hit entity
|
||
|
CEntity* pHitEntity = CPhysics::GetEntityFromInst(it->GetHitInst());
|
||
|
|
||
|
if( pWeaponInfo )
|
||
|
{
|
||
|
if(!flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) && pWeapon->GetDamageType() == DAMAGE_TYPE_EXPLOSIVE)
|
||
|
{
|
||
|
eExplosionTag tag = pWeaponInfo->GetExplosionTag(pHitEntity);
|
||
|
|
||
|
if(tag != EXP_TAG_DONTCARE)
|
||
|
{
|
||
|
// Don't explode if hit material is flagged to pass through (url:bugstar:2074338).
|
||
|
if (!PGTAMATERIALMGR->GetMtlFlagShootThroughFx(it->GetHitMaterialId()))
|
||
|
{
|
||
|
CExplosionManager::CExplosionArgs explosionArgs(tag, it->GetHitPosition());
|
||
|
|
||
|
explosionArgs.m_pEntExplosionOwner = pFiringEntity;
|
||
|
explosionArgs.m_bInAir = false;
|
||
|
explosionArgs.m_vDirection = it->GetHitNormal();
|
||
|
explosionArgs.m_originalExplosionTag = tag;
|
||
|
explosionArgs.m_weaponHash = pWeaponInfo->GetHash();
|
||
|
explosionArgs.m_pAttachEntity = (pHitEntity && pHitEntity->GetDrawable()) ? const_cast<CEntity*>(pHitEntity) : NULL;
|
||
|
|
||
|
if (pFiringEntity && pWeaponInfo->GetFiringEntityIgnoresExplosionDamage())
|
||
|
{
|
||
|
CEntity* pIgnoreEntity = pFiringEntity;
|
||
|
if (pWeaponInfo->GetIsVehicleWeapon() && !pFiringEntity->GetIsTypeVehicle() && pFiringPed && pFiringPed->GetMyVehicle())
|
||
|
{
|
||
|
pIgnoreEntity = pFiringPed->GetMyVehicle();
|
||
|
}
|
||
|
|
||
|
explosionArgs.m_pEntIgnoreDamage = pIgnoreEntity;
|
||
|
}
|
||
|
|
||
|
// Check if a custom explosion shake amplitude is defined for this weapon
|
||
|
const float shakeAmplitude = pWeaponInfo->GetExplosionShakeAmplitude();
|
||
|
if(shakeAmplitude >= 0.0f)
|
||
|
{
|
||
|
explosionArgs.m_fCamShake = shakeAmplitude;
|
||
|
}
|
||
|
|
||
|
if(!bLocalFiringEntity || CExplosionManager::AddExplosion(explosionArgs))
|
||
|
{
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if ( pFiringPed && pFiringPed->IsPlayer() )
|
||
|
{
|
||
|
const CAmmoInfo* pAmmoInfo = pWeaponInfo->GetAmmoInfo(pFiringPed);
|
||
|
if (pAmmoInfo && pAmmoInfo->GetIsExplosive() && !flags.IsFlagSet(CPedDamageCalculator::DF_MeleeDamage))
|
||
|
{
|
||
|
const bool bIsShotgun = pWeaponInfo->GetGroup() == WEAPONGROUP_SHOTGUN;
|
||
|
eExplosionTag tag = bIsShotgun ? EXP_TAG_EXPLOSIVEAMMO_SHOTGUN : EXP_TAG_EXPLOSIVEAMMO;
|
||
|
|
||
|
CExplosionManager::CExplosionArgs explosionArgs(tag, it->GetHitPosition());
|
||
|
explosionArgs.m_pEntExplosionOwner = pFiringEntity;
|
||
|
explosionArgs.m_bInAir = true;
|
||
|
explosionArgs.m_vDirection = it->GetHitNormal();
|
||
|
explosionArgs.m_originalExplosionTag = tag;
|
||
|
explosionArgs.m_fCamShake = g_ExplosiveAmmoShakeAmplitude;
|
||
|
explosionArgs.m_weaponHash = WEAPONTYPE_EXPLOSION;
|
||
|
|
||
|
if (pHitEntity && (pHitEntity->GetIsTypeVehicle() || pHitEntity->GetIsTypePed()))
|
||
|
{
|
||
|
explosionArgs.m_pAttachEntity = pHitEntity;
|
||
|
|
||
|
if(pHitEntity->GetIsTypeVehicle())
|
||
|
{ //Don't use a delay when syncing explosion attached to
|
||
|
//vehicle as this requires immediate syncing to ensure
|
||
|
//coordination with any other syncing of vehicle
|
||
|
//parts like doors opening etc.
|
||
|
explosionArgs.m_activationDelay = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!bLocalFiringEntity || CExplosionManager::AddExplosion(explosionArgs))
|
||
|
{
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
// CHEATS - BANG BANG
|
||
|
else if( pWeaponInfo->GetDamageType() == DAMAGE_TYPE_BULLET && !flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) &&
|
||
|
pFiringPed->GetPlayerInfo()->GetPlayerResetFlags().IsFlagSet(CPlayerResetFlags::PRF_EXPLOSIVE_AMMO_ON) )
|
||
|
{
|
||
|
eExplosionTag tag = EXP_TAG_BULLET;
|
||
|
|
||
|
CExplosionManager::CExplosionArgs explosionArgs(tag, it->GetHitPosition());
|
||
|
explosionArgs.m_pEntExplosionOwner = pFiringEntity;
|
||
|
explosionArgs.m_bInAir = false;
|
||
|
explosionArgs.m_vDirection = it->GetHitNormal();
|
||
|
explosionArgs.m_originalExplosionTag = tag;
|
||
|
explosionArgs.m_fCamShake = g_ExplosiveAmmoShakeAmplitude;
|
||
|
explosionArgs.m_weaponHash = WEAPONTYPE_EXPLOSION;
|
||
|
|
||
|
if(!bLocalFiringEntity || CExplosionManager::AddExplosion(explosionArgs))
|
||
|
{
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
else if (flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) &&
|
||
|
pFiringPed->GetPlayerInfo()->GetPlayerResetFlags().IsFlagSet(CPlayerResetFlags::PRF_EXPLOSIVE_MELEE_ON))
|
||
|
{
|
||
|
if(pHitEntity && (pHitEntity->GetIsTypePed() || pHitEntity->GetIsTypeObject() || pHitEntity->GetIsTypeVehicle()))
|
||
|
{
|
||
|
eExplosionTag tag = EXP_TAG_BULLET;
|
||
|
|
||
|
CExplosionManager::CExplosionArgs explosionArgs(tag, it->GetHitPosition());
|
||
|
explosionArgs.m_pEntExplosionOwner = pFiringEntity;
|
||
|
explosionArgs.m_bInAir = true;
|
||
|
explosionArgs.m_vDirection = VEC3V_TO_VECTOR3(pFiringPed->GetTransform().GetForward());
|
||
|
explosionArgs.m_originalExplosionTag = tag;
|
||
|
explosionArgs.m_bDisableDamagingOwner = true;
|
||
|
explosionArgs.m_fCamShake = g_ExplosiveMeleeShakeAmplitude;
|
||
|
explosionArgs.m_weaponHash = WEAPONTYPE_EXPLOSION;
|
||
|
|
||
|
if(!bLocalFiringEntity || CExplosionManager::AddExplosion(explosionArgs))
|
||
|
{
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const phGlassInst* pGlassInst = NULL;
|
||
|
if(it->GetHitInst()->GetClassType() == phInst::PH_INST_GLASS)
|
||
|
{
|
||
|
pGlassInst = static_cast<const phGlassInst*>(it->GetHitInst());
|
||
|
#if BREAKABLE_GLASS_USE_BVH
|
||
|
if(!pGlassInst->GetIsElementActive(it->GetHitPartIndex()))
|
||
|
#else
|
||
|
if(!pGlassInst->GetIsHitActive(it->GetHitPosition()))
|
||
|
#endif // BREAKABLE_GLASS_USE_BVH
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check to see if this is a clone parachute. If it is, don't apply impacts to it locally (netblender + physics can mess it up)
|
||
|
CObject* pObject = static_cast<CObject*>(pHitEntity);
|
||
|
if(pHitEntity && pHitEntity->GetIsTypeObject() && pObject->GetIsParachute() && pObject->IsNetworkClone())
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// Don't let anything shoot itself
|
||
|
if(pHitEntity && pHitEntity == pFiringEntity)
|
||
|
{
|
||
|
weaponAssertf(0, "Something shot itself");
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
if (NetworkInterface::IsGameInProgress() && pHitEntity)
|
||
|
{
|
||
|
bool bIsFriendlyFireAllowed = true;
|
||
|
if (pHitEntity->GetIsTypePed())
|
||
|
bIsFriendlyFireAllowed = NetworkInterface::IsFriendlyFireAllowed((const CPed*) pHitEntity, pFiringEntity);
|
||
|
else if (pHitEntity->GetIsTypeVehicle())
|
||
|
bIsFriendlyFireAllowed = NetworkInterface::IsFriendlyFireAllowed((const CVehicle*) pHitEntity, pFiringEntity);
|
||
|
|
||
|
|
||
|
bool bFirerInSameVehicleAsHitEntity = false;
|
||
|
CVehicle* pVehicle = NULL;
|
||
|
|
||
|
if (pHitEntity->GetIsTypeVehicle())
|
||
|
{
|
||
|
pVehicle = static_cast<CVehicle*>(pHitEntity);
|
||
|
if (pVehicle)
|
||
|
{
|
||
|
if (pFiringPed && pFiringPed->GetIsInVehicle() && (pFiringPed->GetMyVehicle() == pVehicle))
|
||
|
bFirerInSameVehicleAsHitEntity = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool bIsInFirstPerson = CPedFactory::GetFactory()->GetLocalPlayer()->IsInFirstPersonVehicleCamera();
|
||
|
bool bGlassHit = PGTAMATERIALMGR->GetIsSmashableGlass(it->GetHitMaterialId());
|
||
|
|
||
|
// Only display vfx, no damage/smashing/decals, except if we're hitting windows from inside in 1st person.
|
||
|
if (!bIsFriendlyFireAllowed && !(bFirerInSameVehicleAsHitEntity && bIsInFirstPerson && bGlassHit))
|
||
|
{
|
||
|
if (pHitEntity->GetIsTypeVehicle())
|
||
|
{
|
||
|
DoWeaponImpactVfx(pWeapon, pFiringEntity, vWeaponPos, &(*it), 0.0f, flags|CPedDamageCalculator::DF_PtFxOnly, false, false, bTemporaryNetworkWeapon);
|
||
|
}
|
||
|
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
if (bFirerInSameVehicleAsHitEntity)
|
||
|
if (!pVehicle->GetVehicleDamage()->CanVehicleBeDamagedBasedOnDriverInvincibility())
|
||
|
return 0.0f;
|
||
|
|
||
|
//----------------------------------------------------------------------
|
||
|
//Prevent double application of damage on the machine that owns the ped.
|
||
|
//Ensure that the damage comes in from the remote killer through the receivefiremessage and that is processed and the doweaponimpact from the bullet process damage is ignored.
|
||
|
//Skip the processing here if the hitped is local, the firing entity is remote, and this method wasn't called from receivefiremessage.
|
||
|
//Allow it otherwise, local both, remote hitped, etc...
|
||
|
//
|
||
|
//This will fix issues where shotgun blasts were sending the local player victim flying. B* 1444873. lavalley
|
||
|
//
|
||
|
//If we are firing at a non-networked entity such as a vending machine don't do this processing. B* 1692001. lavalley (added pHitEntity network object check to if below)
|
||
|
if (!bFirerInSameVehicleAsHitEntity && !bReceiveFireMessage && !flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) && NetworkUtils::GetNetworkObjectFromEntity(pHitEntity))
|
||
|
{
|
||
|
const bool bHitEntityLocal = !NetworkUtils::GetNetworkObjectFromEntity(pHitEntity) || !NetworkUtils::IsNetworkClone(pHitEntity);
|
||
|
const bool bFiringEntityLocal = !NetworkUtils::GetNetworkObjectFromEntity(pFiringEntity) || !NetworkUtils::IsNetworkClone(pFiringEntity);
|
||
|
|
||
|
|
||
|
if (!bHitEntityLocal && !bFiringEntityLocal)
|
||
|
{
|
||
|
weaponDebugf3("FiringEntity(%s) - HitEntity(%s)", pFiringEntity ? pFiringEntity->GetModelName() : "", pHitEntity ? pHitEntity->GetModelName() : "");
|
||
|
if (pHitEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed *pHitPed = static_cast<CPed*> (pHitEntity);
|
||
|
WorldProbe::CShapeTestHitPoint* pResult = &(*it);
|
||
|
if (pResult && !pHitPed->GetPedResetFlag(CPED_RESET_FLAG_BlockIkWeaponReactions))
|
||
|
{
|
||
|
Vec3V vPosition(VECTOR3_TO_VEC3V(pResult->GetHitPosition()));
|
||
|
Vec3V vDirection(NormalizeFast(Subtract(vPosition, VECTOR3_TO_VEC3V(vWeaponPos))));
|
||
|
CIkRequestBodyReact bodyReactRequest(vPosition, vDirection, (int)pResult->GetHitComponent());
|
||
|
bodyReactRequest.SetWeaponGroup(pWeaponInfo->GetGroup());
|
||
|
bodyReactRequest.SetLocalInflictor(false);
|
||
|
pHitPed->GetIkManager().Request(bodyReactRequest);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bHitEntityLocal && !bFiringEntityLocal)
|
||
|
{
|
||
|
weaponDebugf3("bHitEntityLocal && !bFiringEntityLocal --> return 0.0f");
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
//----------------------------------------------------------------------
|
||
|
}
|
||
|
|
||
|
#if __ASSERT
|
||
|
f32 fNormalMag = it->GetHitNormal().Mag();
|
||
|
weaponAssertf(fNormalMag >= 0.5f && fNormalMag <= 1.5f, "Weapon impact normal not normalised %.4f (%.2f, %.2f, %.2f) %s", fNormalMag, it->GetHitNormal().x, it->GetHitNormal().y, it->GetHitNormal().z, pHitEntity ? pHitEntity->GetModelName() : "");
|
||
|
#endif // __ASSERT
|
||
|
|
||
|
if(pFiringPed)
|
||
|
{
|
||
|
CPed* pHitPed = (pHitEntity && pHitEntity->GetIsTypePed()) ? static_cast<CPed*>(pHitEntity) : NULL;
|
||
|
if(!pHitPed)
|
||
|
{
|
||
|
pFiringPed->SetPedResetFlag( CPED_RESET_FLAG_MeleeStrikeAgainstNonPed, true );
|
||
|
|
||
|
if(pHitEntity && pHitEntity->GetIsTypeObject())
|
||
|
{
|
||
|
CObject* pHitObj = static_cast<CObject*>(pHitEntity);
|
||
|
CEntity* pHitAttachParent = (CPhysical *) pHitObj->GetAttachParent();
|
||
|
if(pHitAttachParent && pHitAttachParent->GetIsTypePed())
|
||
|
{
|
||
|
pHitPed = static_cast<CPed*>(pHitAttachParent);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Don't let AI ped's that are friends shoot each other
|
||
|
bool bIsHitPedFriendly = pHitPed && pFiringPed->GetPedIntelligence()->IsFriendlyWith( *pHitPed );
|
||
|
if(!pFiringPed->IsPlayer() && bIsHitPedFriendly)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check if a ped within range has been hit in this shot
|
||
|
bool bHeadShotNearby = false;
|
||
|
if(fApplyDamage > 0.0f && pWeaponInfo && pWeaponInfo->GetIsGun() && pWeaponInfo->GetDamageType() != DAMAGE_TYPE_ELECTRIC)
|
||
|
{
|
||
|
for(WorldProbe::ResultIterator it2 = refResults.begin(); it2 < refResults.last_result(); ++it2)
|
||
|
{
|
||
|
if(it2 != it && PGTAMATERIALMGR->GetIsPed(it2->GetHitMaterialId()))
|
||
|
{
|
||
|
Vector3 v = it2->GetHitPosition() - it->GetHitPosition();
|
||
|
if(v.Mag2() < 1.0f)
|
||
|
{
|
||
|
bHeadShotNearby = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Damage
|
||
|
float fDamage = fApplyDamage * fDamageMod;
|
||
|
//B*1783494: Scale harpoon damage if underwater or has been shot in the head/torso
|
||
|
if (pWeaponInfo->GetIsUnderwaterGun() && pHitEntity && pHitEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed *pTargetPed = static_cast<CPed*>(pHitEntity);
|
||
|
if (pTargetPed)
|
||
|
{
|
||
|
static dev_float fDamageMultiplier = 5.0f;
|
||
|
s32 iHitComponent = it->GetHitComponent();
|
||
|
s32 iHitBoneTag = pTargetPed->GetBoneTagFromRagdollComponent(iHitComponent);
|
||
|
bool bHitHeadOrTorso = iHitBoneTag == BONETAG_NECK || iHitBoneTag == BONETAG_HEAD || iHitBoneTag == BONETAG_NECKROLL || iHitBoneTag == BONETAG_ROOT
|
||
|
|| iHitBoneTag == BONETAG_SPINE0 || iHitBoneTag == BONETAG_SPINE1 || iHitBoneTag == BONETAG_SPINE2 || iHitBoneTag == BONETAG_SPINE3;
|
||
|
|
||
|
if (pTargetPed->GetIsSwimming() || bHitHeadOrTorso)
|
||
|
{
|
||
|
fDamage *= fDamageMultiplier;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// Apply falloff scaling (do not apply for melee weapons)
|
||
|
if(!pFiringEntity || !pFiringEntity->GetIsDynamic() || !static_cast<CDynamicEntity*>(pFiringEntity)->IsNetworkClone())
|
||
|
{
|
||
|
fDamage = ApplyFallOffDamageModifier(pHitEntity, pWeaponInfo, vWeaponPos, fFallOffRangeModifier, fFallOffDamageModifier, fDamage, pFiringEntity);
|
||
|
}
|
||
|
if(pHitEntity && pHitEntity->GetIsTypePed())
|
||
|
{
|
||
|
f32 fPedDamage = fDamage;
|
||
|
if(pModifiers)
|
||
|
{
|
||
|
fPedDamage *= pModifiers->fPedDamageMult;
|
||
|
}
|
||
|
|
||
|
bool bGenerateVFX = bShouldGenerateVFX;
|
||
|
|
||
|
fDamageDone += DoWeaponImpactPed(pWeapon, pFiringEntity, vWeaponPos, &(*it), fPedDamage, bTemporaryNetworkWeapon, flags, pMeleeInfo, fRecoilAccuracyWhenFired, &bGenerateVFX, damageAggregationCount);
|
||
|
|
||
|
// For peds this needs to be done AFTER DoWeaponImpactPed in case the ped's ragdoll was activated
|
||
|
if (bGenerateVFX)
|
||
|
{
|
||
|
DoWeaponImpactVfx(pWeapon, pFiringEntity, vWeaponPos, &(*it), fPedDamage, flags, bHeadShotNearby, false, bTemporaryNetworkWeapon);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bool bShouldApplyDamageToGlass = true;
|
||
|
|
||
|
if( pWeaponInfo )
|
||
|
{
|
||
|
// Set Breakable Glass Crack Id.
|
||
|
s32 crackId = g_vfxWeapon.GetBreakableGlassId(pWeaponInfo->GetEffectGroup());
|
||
|
CPhysics::SetSelectedCrack(crackId);
|
||
|
|
||
|
// Bullet Resistant Glass: Don't smash/damage glass when hit with a melee weapon.
|
||
|
if (pWeaponInfo->GetIsMelee() && pHitEntity->GetIsTypeVehicle())
|
||
|
{
|
||
|
CVehicle *pVehicle = static_cast<CVehicle*>(pHitEntity);
|
||
|
if (pVehicle && pVehicle->HasBulletResistantGlass())
|
||
|
{
|
||
|
bShouldApplyDamageToGlass = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add a broken glass event (in mp only)- not just when the glass fragments, but whenever you hit glass.
|
||
|
if (NetworkInterface::IsGameInProgress() && pFiringEntity && bShouldApplyDamageToGlass)
|
||
|
{
|
||
|
bool bIsGlassForAIPurposes = PGTAMATERIALMGR->GetIsGlass(it->GetHitMaterialId());
|
||
|
if (bIsGlassForAIPurposes)
|
||
|
{
|
||
|
CEventShockingBrokenGlass event(*pFiringEntity, it->GetHitPositionV());
|
||
|
CShockingEventsManager::Add(event);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This needs done before any fragments have broken off
|
||
|
DoWeaponImpactVfx(pWeapon, pFiringEntity, vWeaponPos, &(*it), fDamage, flags, bHeadShotNearby, false, bTemporaryNetworkWeapon, bShouldApplyDamageToGlass);
|
||
|
|
||
|
if(pGlassInst)
|
||
|
{
|
||
|
fDamageDone += DoWeaponImpactBreakableGlass(pWeapon, pFiringEntity, vWeaponPos, &(*it), fDamage);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(pHitEntity)
|
||
|
{
|
||
|
f32 fDoDamage = fDamage;
|
||
|
|
||
|
if(pHitEntity->GetIsTypeVehicle())
|
||
|
{
|
||
|
if(pModifiers)
|
||
|
{
|
||
|
fDoDamage *= pModifiers->fVehDamageMult;
|
||
|
}
|
||
|
|
||
|
// Include the firing ped damage modifier as defined by script
|
||
|
// Only want to do this for local players here, remote players apply the modifier through CWeapon::SendFireMessage
|
||
|
if (pFiringPed && pFiringPed->IsLocalPlayer() && pWeaponInfo->GetDamageType() == DAMAGE_TYPE_MELEE)
|
||
|
{
|
||
|
CPlayerInfo* pPlayerInfo = pFiringPed->GetPlayerInfo();
|
||
|
if (pPlayerInfo)
|
||
|
{
|
||
|
fDoDamage *= pPlayerInfo->GetPlayerMeleeDamageModifier(pWeaponInfo->GetIsUnarmed());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fDamageDone += DoWeaponImpactCar(pWeapon, pFiringEntity, vWeaponPos, &(*it), fDoDamage, bFireDriveby, bTemporaryNetworkWeapon, flags, hitDirOut, hitDirIn, bShouldApplyDamageToGlass);
|
||
|
}
|
||
|
else if(pHitEntity->GetIsTypeObject())
|
||
|
{
|
||
|
// NOTE: pure cloth ( there are no cloth and non-cloth parts in the same frag) impact is not processed here anymore
|
||
|
// see CBullet::ComputeImpacts
|
||
|
// Svetli
|
||
|
fragInst* pFragInst = pHitEntity->GetFragInst();
|
||
|
bool bOnlyCloth = false;
|
||
|
if( pFragInst && pFragInst->GetType() && (pFragInst->GetType()->GetNumEnvCloths() > 0) )
|
||
|
{
|
||
|
bOnlyCloth = (NULL == pFragInst->GetType()->GetClothDrawable()) ;
|
||
|
}
|
||
|
|
||
|
if( !bOnlyCloth )
|
||
|
{
|
||
|
if( pWeaponInfo && pWeaponInfo->GetIsGun())
|
||
|
{
|
||
|
pHitEntity->ProcessFxEntityShot(it->GetHitPosition(), it->GetHitNormal(), it->GetHitComponent(), pWeaponInfo, pFiringEntity);
|
||
|
}
|
||
|
|
||
|
if(pModifiers)
|
||
|
{
|
||
|
fDoDamage *= pModifiers->fObjDamageMult;
|
||
|
}
|
||
|
|
||
|
fDamageDone += DoWeaponImpactObject(pWeapon, pFiringEntity, vWeaponPos, &(*it), fDoDamage, bTemporaryNetworkWeapon, flags, it->GetHitMaterialId());
|
||
|
|
||
|
if(it->GetHitInst() && it->GetHitInst()->IsInLevel() && (CPhysics::GetLevel()->GetInstanceTypeFlags(it->GetHitInst()->GetLevelIndex()) & ArchetypeFlags::GTA_PROJECTILE_TYPE))
|
||
|
{
|
||
|
fDamageDone += DoWeaponImpactWeapon(pWeapon, pFiringEntity, vWeaponPos, &(*it), fDamage, bTemporaryNetworkWeapon, flags, 0, networkWeaponImpactInfo, fRecoilAccuracyWhenFired, damageAggregationCount);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pHitEntity->ProcessFxEntityShot(it->GetHitPosition(), it->GetHitNormal(), it->GetHitComponent(), pWeaponInfo, pFiringEntity);
|
||
|
fDamageDone += fDamage;
|
||
|
}
|
||
|
|
||
|
// Do not do perform recoils on broken frag instances
|
||
|
bool bIsBroken = false;
|
||
|
|
||
|
// Analyze to see if the next hit will break this frag
|
||
|
if( IsFragInst( it->GetHitInst() ) )
|
||
|
{
|
||
|
const fragInst* pFragInstance = static_cast<const fragInst*>( it->GetHitInst() );
|
||
|
bIsBroken = pFragInstance->GetChildBroken( it->GetHitComponent() );
|
||
|
}
|
||
|
|
||
|
// Recoil direction threshold has been moved earlier to avoid sfx/vfx on surfaces we throw out
|
||
|
if( pFiringPed && flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) && !bIsBroken)
|
||
|
{
|
||
|
// This should always be true
|
||
|
CTaskMelee* pFiringPedMeleeTask = pFiringPed->GetPedIntelligence()->GetTaskMelee();
|
||
|
if( pFiringPedMeleeTask )
|
||
|
{
|
||
|
CTaskMeleeActionResult* pMeleeTaskActionResult = pFiringPed->GetPedIntelligence()->GetTaskMeleeActionResult();
|
||
|
const CActionResult* pActionResult = pMeleeTaskActionResult ? pMeleeTaskActionResult->GetActionResult() : NULL;
|
||
|
if( pActionResult && pActionResult->GetIsValidForRecoil() )
|
||
|
{
|
||
|
// Check to see if we can find an appropriate hit reaction that is available to do.
|
||
|
CActionFlags::ActionTypeBitSetData actionTypeBitSet;
|
||
|
actionTypeBitSet.Set( CActionFlags::AT_RECOIL, true );
|
||
|
const CActionDefinition* pMeleeActionToDo = ACTIONMGR.SelectSuitableAction(
|
||
|
actionTypeBitSet,// The type we want to search in.
|
||
|
NULL, // No hit combo built up
|
||
|
true,// Test whether or not it is an entry action.
|
||
|
true,// Only find entry actions.
|
||
|
pFiringPed,
|
||
|
pActionResult->GetID(),
|
||
|
pActionResult->GetPriority(),
|
||
|
pFiringPed,
|
||
|
false,
|
||
|
false,
|
||
|
false,
|
||
|
CActionManager::ShouldDebugPed( pFiringPed ) );
|
||
|
|
||
|
const CActionResult* pActionResultToForce = pMeleeActionToDo ? pMeleeActionToDo->GetActionResult( pFiringPed ) : NULL;
|
||
|
if( pActionResultToForce )
|
||
|
{
|
||
|
pFiringPedMeleeTask->SetLocalReaction( pActionResultToForce, true, pMeleeInfo ? pMeleeInfo->uNetworkActionID : 0 );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Do player shooting specific code
|
||
|
if(pFiringPed && pFiringPed->IsPlayer())
|
||
|
{
|
||
|
if(!pFiringPed->IsNetworkClone())
|
||
|
{
|
||
|
DoWeaponFiredByPlayer(pWeapon, pFiringPed, vWeaponPos, &(*it), fDamageDone, flags);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AI
|
||
|
DoWeaponImpactAI(pWeapon, pFiringPed, vWeaponPos, &(*it), fDamageDone);
|
||
|
|
||
|
bool justHitWater = false;
|
||
|
if(!isUnderWater)
|
||
|
{
|
||
|
if(it->GetHitInst() && it->GetHitInst()->IsInLevel() && (CPhysics::GetLevel()->GetInstanceTypeFlags(it->GetHitInst()->GetLevelIndex()) & ArchetypeFlags::GTA_RIVER_TYPE))
|
||
|
{
|
||
|
isUnderWater = true;
|
||
|
justHitWater = true;
|
||
|
}
|
||
|
}
|
||
|
f32 cameraDepth = Water::GetCameraWaterDepth();
|
||
|
// Trigger a max of two impact sounds, and only if they are on different surface materials
|
||
|
if( !flags.IsFlagSet( CPedDamageCalculator::DF_SuppressImpactAudio ) && it->GetHitMaterialId() != lastMaterialForAudio && uNumImpactsForAudio < 2 && (!isUnderWater || ( cameraDepth > 0 || (isUnderWater && justHitWater))))
|
||
|
{
|
||
|
DoWeaponImpactAudio(pWeapon, pFiringEntity, vWeaponPos, &(*it), fDamageDone, flags );
|
||
|
lastMaterialForAudio = it->GetHitMaterialId();
|
||
|
uNumImpactsForAudio++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fDamageDone;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
f32 CWeaponDamage::GeneratePedDamageEvent(CEntity* pFiringEntity, CPed* pHitPed, const u32 uWeaponHash, const f32 fWeaponDamage, const Vector3& vStart, WorldProbe::CShapeTestHitPoint* pResult, const fwFlags32& flags, const CWeapon* UNUSED_PARAM(pWeapon), const s32 iHitComponent, f32 fForce, const sMeleeInfo *pMeleeInfo, bool bDeferRagdollActivation, const float fRecoilAccuracyWhenFired, bool bTemporaryNetworkWeapon, u8 damageAggregationCount)
|
||
|
{
|
||
|
weaponDebugf3("CWeaponDamage::GeneratePedDamageEvent");
|
||
|
|
||
|
// Get the weapon info
|
||
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(uWeaponHash);
|
||
|
if(!pWeaponInfo)
|
||
|
{
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
if(!pHitPed)
|
||
|
{
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
s32 iHitBoneTag = 0;
|
||
|
f32 fHitDir = 0.0f;
|
||
|
|
||
|
// Extract hit information from the intersection data.
|
||
|
if(pResult)
|
||
|
{
|
||
|
iHitBoneTag = pHitPed->GetBoneTagFromRagdollComponent(pResult->GetHitComponent());
|
||
|
Vector3 vTempDir = vStart - pResult->GetHitPosition();
|
||
|
fHitDir = rage::Atan2f(-vTempDir.x, vTempDir.y);
|
||
|
fHitDir -= pHitPed->GetTransform().GetHeading();
|
||
|
fHitDir = fwAngle::LimitRadianAngle(fHitDir);
|
||
|
}
|
||
|
|
||
|
// Optional ragdoll reactions.
|
||
|
bool bUseRagdollEvent = false;
|
||
|
bool bUseRagdollReaction = false;
|
||
|
bool bUseBodyIkReaction = false;
|
||
|
Vector3 vRagdollImpulseDir(Vector3::ZeroType);
|
||
|
f32 fRagdollImpulseMag = 0.0f;
|
||
|
|
||
|
// An additional force that can be applied, if the ped is rag dolled
|
||
|
// Used to throw peds out of vehicles
|
||
|
Vector3 vAdditionalRagdollForce(Vector3::ZeroType);
|
||
|
|
||
|
// Todo: add to metadata
|
||
|
// if( pHitPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && pHitPed->GetMyVehicle() )
|
||
|
// {
|
||
|
// // BAD_SEAT_USE
|
||
|
// s32 seat = pHitPed->GetMyVehicle()->GetPedsSeatIndex(pHitPed);
|
||
|
// // Extra force out of the sides of the heli
|
||
|
// if( pHitPed->GetMyVehicle()->GetVehicleType() == VEHICLE_TYPE_HELI && !pHitPed->GetMyVehicle()->IsSeatAFrontSeat(seat) )
|
||
|
// {
|
||
|
// vAdditionalRagdollForce = pHitPed->GetMass()*sfAdditionalVehicleExitForceScale*pHitPed->GetB();
|
||
|
// }
|
||
|
// // Extra force out of the back of the van, only if the doors are open
|
||
|
// else if( pHitPed->GetMyVehicle()->GetVehicleModelInfo()->GetFlag(FLAG_IS_VAN) && !pHitPed->GetMyVehicle()->IsSeatAFrontSeat(seat) )
|
||
|
// {
|
||
|
// eHierarchyId iDoor = pHitPed->GetMyVehicle()->GetEntryDoorForSeat(seat);
|
||
|
// CCarDoor* pDoor = pHitPed->GetMyVehicle()->GetDoorFromId(iDoor);
|
||
|
// if( pDoor )
|
||
|
// pDoor->SetTargetDoorOpenRatio(1.0f, CCarDoor::DRIVEN_AUTORESET);
|
||
|
//
|
||
|
// vAdditionalRagdollForce = pHitPed->GetMass()*sfAdditionalVehicleExitForceScale*-pHitPed->GetMyVehicle()->GetB();
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
// Calculate the distance to the ped we have hit
|
||
|
float fDist = FLT_MAX;
|
||
|
if(pResult)
|
||
|
{
|
||
|
fDist = vStart.Dist(pResult->GetHitPosition());
|
||
|
weaponDebugf3("vStart[%f %f %f] GetHitPosition[%f %f %f] fDist[%f]",vStart.x,vStart.y,vStart.z,pResult->GetHitPosition().x,pResult->GetHitPosition().y,pResult->GetHitPosition().x,fDist);
|
||
|
}
|
||
|
else if(pHitPed)
|
||
|
{
|
||
|
Vector3 vHitPedPosition = VEC3V_TO_VECTOR3(pHitPed->GetTransform().GetPosition());
|
||
|
fDist = vStart.Dist(vHitPedPosition);
|
||
|
weaponDebugf3("vStart[%f %f %f] pHitPed[%f %f %f] fDist[%f]",vStart.x,vStart.y,vStart.z,vHitPedPosition.x,vHitPedPosition.y,vHitPedPosition.z,fDist);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fDist = 0.f;
|
||
|
weaponDebugf3("else fDist = 0.f");
|
||
|
}
|
||
|
|
||
|
fwFlags32 modifiedFlags = flags;
|
||
|
if(!modifiedFlags.IsFlagSet(CPedDamageCalculator::DF_AllowHeadShot))
|
||
|
{
|
||
|
if(pWeaponInfo->GetDoesRecoilAccuracyAllowHeadShotModifier(pHitPed, fRecoilAccuracyWhenFired, fDist))
|
||
|
{
|
||
|
modifiedFlags.SetFlag(CPedDamageCalculator::DF_AllowHeadShot);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Actually calculate and apply the damage to peds health/armour and then
|
||
|
// check if the damage calculator thought the damage should affect the
|
||
|
// ped in other ways as well.
|
||
|
bool bAllowTempDamageEventToBeAdded = true;
|
||
|
CEventDamage tempDamageEvent(pFiringEntity, fwTimer::GetTimeInMilliseconds(), uWeaponHash);
|
||
|
if(flags.IsFlagSet(CPedDamageCalculator::DF_DontReportCrimes))
|
||
|
{
|
||
|
tempDamageEvent.SetCanReportCrimes(false);
|
||
|
}
|
||
|
|
||
|
u32 uParentMeleeActionResultID = pMeleeInfo ? pMeleeInfo->uParentMeleeActionResultID : 0;
|
||
|
|
||
|
CPedDamageCalculator damageCalculator(pFiringEntity, fWeaponDamage, uWeaponHash, iHitComponent, false, damageAggregationCount);
|
||
|
weaponDebugf3("invoke ApplyDamageAndComputeResponse hitComponent[%d]",iHitComponent);
|
||
|
damageCalculator.ApplyDamageAndComputeResponse(pHitPed, tempDamageEvent.GetDamageResponseData(), modifiedFlags, uParentMeleeActionResultID, fDist);
|
||
|
|
||
|
// Flag an event with the firing entity (if it's a ped) that it's hit another ped with weapon damage
|
||
|
if (pFiringEntity && pFiringEntity->GetIsTypePed() && !pHitPed->IsDead() && pHitPed != pFiringEntity)
|
||
|
{
|
||
|
CPed *pFiringEntityPed = static_cast<CPed *>(pFiringEntity);
|
||
|
|
||
|
// only display hit markers when we are not within the same group
|
||
|
if( !pFiringEntityPed->GetPedIntelligence()->IsFriendlyWith(*pHitPed) )
|
||
|
{
|
||
|
pFiringEntityPed->SetPedResetFlag(CPED_RESET_FLAG_HitPedWithWeapon, true);
|
||
|
|
||
|
// Change AI hit ped target if previously targeting another ped
|
||
|
if( flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) && !pHitPed->IsAPlayerPed() )
|
||
|
{
|
||
|
CTaskMelee* pOpponentTaskMelee = pHitPed->GetPedIntelligence() ? pHitPed->GetPedIntelligence()->GetTaskMelee() : NULL;
|
||
|
if( pOpponentTaskMelee && ( pHitPed->GetPedConfigFlag( CPED_CONFIG_FLAG_CanAttackFriendly ) || !CActionManager::ArePedsFriendlyWithEachOther( pFiringEntityPed, pHitPed ) ) )
|
||
|
pOpponentTaskMelee->SetTargetEntity( pFiringEntity, true );
|
||
|
}
|
||
|
}
|
||
|
// If you're in freemode, and you're shooting a player ped, force the icon on.
|
||
|
else if(fWeaponDamage > 0.0f &&
|
||
|
NetworkInterface::IsNetworkOpen() && NetworkInterface::FriendlyFireAllowed())
|
||
|
{
|
||
|
pFiringEntityPed->SetPedResetFlag(CPED_RESET_FLAG_HitPedWithWeapon, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// don't do nm reaction behaviours if the ped has specifically asked to not ragdoll untill dead
|
||
|
if (pHitPed->GetPedResetFlag(CPED_RESET_FLAG_BlockWeaponReactionsUnlessDead) && !pHitPed->ShouldBeDead())
|
||
|
{
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
// Collect information from melee combat if available.
|
||
|
// Note: Pistol whips and gun butts can cause melee reactions, so don't
|
||
|
// filter this code based on weapon type...
|
||
|
// Note: This possibly sets bUseRagdollReaction and the vRagdollImpulse.
|
||
|
bool bIsStandardMeleeReaction = false;
|
||
|
bool bTriggerDrowning = false;
|
||
|
|
||
|
if(modifiedFlags.IsFlagSet(CPedDamageCalculator::DF_MeleeDamage))
|
||
|
{
|
||
|
tempDamageEvent.SetMeleeDamage(true);
|
||
|
}
|
||
|
|
||
|
const CActionDefinition* pMeleeActionToDo = NULL;
|
||
|
if(uWeaponHash != 0 &&
|
||
|
pFiringEntity &&
|
||
|
pFiringEntity->GetIsTypePed() &&
|
||
|
(modifiedFlags.IsFlagSet(CPedDamageCalculator::DF_MeleeDamage) ||
|
||
|
modifiedFlags.IsFlagSet(CPedDamageCalculator::DF_SelfDamage)))
|
||
|
{
|
||
|
CPed* pFiringPed = static_cast<CPed*>( pFiringEntity );
|
||
|
|
||
|
if( pResult )
|
||
|
{
|
||
|
if (!pHitPed->GetPedResetFlag(CPED_RESET_FLAG_WasHitByVehicleMelee))
|
||
|
CTaskMeleeActionResult::HitImpulseCalculation( pResult, -1.0f, vRagdollImpulseDir, fRagdollImpulseMag, pHitPed );
|
||
|
else
|
||
|
CTaskMountThrowProjectile::HitImpulseCalculation( vRagdollImpulseDir, fRagdollImpulseMag, *pHitPed, *pFiringPed );
|
||
|
}
|
||
|
|
||
|
CTaskMeleeActionResult* pPedMeleeActionResult = pFiringPed->GetPedIntelligence() ? pFiringPed->GetPedIntelligence()->GetTaskMeleeActionResult() : NULL;
|
||
|
if( pPedMeleeActionResult )
|
||
|
pPedMeleeActionResult->ProcessPostHitResults( pFiringPed, pHitPed, false, VECTOR3_TO_VEC3V( vRagdollImpulseDir ), fRagdollImpulseMag );
|
||
|
|
||
|
// Check if a simple melee action task is playing on the damage parent.
|
||
|
// And only try to collect the rest of the melee data if it is a melee reaction.
|
||
|
bIsStandardMeleeReaction = true;
|
||
|
bool bDoStandardMeleeReactionCheck = true;
|
||
|
|
||
|
//! If we don't see ped doing melee task, don't do an animated reaction - it'll probably look a bit weird. This should be pretty
|
||
|
//! rare.
|
||
|
CTaskMeleeActionResult* pFiringPedMeleeActionResultTask = pFiringPed->GetPedIntelligence()->GetTaskMeleeActionResult();
|
||
|
|
||
|
static dev_bool s_bDoAnimatedReactsIfNotRunningMeleeTask = true;
|
||
|
if(!s_bDoAnimatedReactsIfNotRunningMeleeTask)
|
||
|
{
|
||
|
if(bTemporaryNetworkWeapon)
|
||
|
{
|
||
|
u16 meleeID = pMeleeInfo ? pMeleeInfo->uNetworkActionID : 0;
|
||
|
|
||
|
if(!pFiringPedMeleeActionResultTask || (pFiringPedMeleeActionResultTask->GetUniqueNetworkActionID() != meleeID) )
|
||
|
{
|
||
|
bDoStandardMeleeReactionCheck = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(flags.IsFlagSet(CPedDamageCalculator::DF_NoAnimatedMeleeReaction))
|
||
|
{
|
||
|
bDoStandardMeleeReactionCheck = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Determine if we should do a standard check to find a melee reaction to do.
|
||
|
if(modifiedFlags.IsFlagSet(CPedDamageCalculator::DF_SelfDamage) && tempDamageEvent.GetDamageResponseData().m_bKilled && CTaskNMBehaviour::CanUseRagdoll(pHitPed, RAGDOLL_TRIGGER_MELEE, pFiringEntity, 0.0f))
|
||
|
{
|
||
|
bDoStandardMeleeReactionCheck = false;
|
||
|
bUseRagdollReaction = true;
|
||
|
}
|
||
|
else if(pHitPed->GetPedConfigFlag( CPED_CONFIG_FLAG_IsHandCuffed ) ||
|
||
|
pHitPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) ||
|
||
|
#if ENABLE_DRUNK
|
||
|
pHitPed->GetPedConfigFlag( CPED_CONFIG_FLAG_IsDrunk ) ||
|
||
|
#endif // ENABLE_DRUNK
|
||
|
pHitPed->GetIsInCover() ||
|
||
|
pHitPed->GetPedResetFlag( CPED_RESET_FLAG_IsFalling ) ||
|
||
|
pHitPed->GetPedResetFlag( CPED_RESET_FLAG_IsJumping ) ||
|
||
|
pHitPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_ENTER_VEHICLE_SEAT ) )
|
||
|
{
|
||
|
if( !pHitPed->IsNetworkClone() && CTaskNMBehaviour::CanUseRagdoll( pHitPed, RAGDOLL_TRIGGER_MELEE, pFiringEntity, 0.0f ) )
|
||
|
bUseRagdollReaction = true;
|
||
|
else
|
||
|
bUseBodyIkReaction = true;
|
||
|
|
||
|
bDoStandardMeleeReactionCheck = false;
|
||
|
}
|
||
|
else if(!modifiedFlags.IsFlagSet(CPedDamageCalculator::DF_MeleeDamage))
|
||
|
{
|
||
|
if( !pHitPed->IsNetworkClone() && pHitPed->IsFatallyInjured() && CTaskNMBehaviour::CanUseRagdoll( pHitPed, RAGDOLL_TRIGGER_MELEE, pFiringEntity, 0.0f ) )
|
||
|
bUseRagdollReaction = true;
|
||
|
|
||
|
//! Don't do any animated reactions from remote melee damage events. We want animations to sync so
|
||
|
//! only reaction from local hit.
|
||
|
bDoStandardMeleeReactionCheck = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Check to see if the attack was from behind
|
||
|
bool bPreferRagdollReaction = false;
|
||
|
|
||
|
CTask* pTaskUseScenarioBase = pHitPed->GetPedIntelligence()->FindTaskActiveByType( CTaskTypes::TASK_USE_SCENARIO );
|
||
|
if( pTaskUseScenarioBase )
|
||
|
{
|
||
|
CTaskUseScenario* pTaskUseScenario = static_cast<CTaskUseScenario*>(pTaskUseScenarioBase);
|
||
|
const CScenarioInfo& rScenarioInfo = pTaskUseScenario->GetScenarioInfo();
|
||
|
if( rScenarioInfo.GetIsFlagSet( CScenarioInfoFlags::PreferMeleeRagdoll ) )
|
||
|
bPreferRagdollReaction = true;
|
||
|
}
|
||
|
|
||
|
const CPlayerSpecialAbility* pSpecialAbility = pFiringPed->GetSpecialAbility();
|
||
|
if( pSpecialAbility && pSpecialAbility->GetType() == SAT_RAGE && pSpecialAbility->IsActive() )
|
||
|
bPreferRagdollReaction = true;
|
||
|
|
||
|
if (pHitPed->GetPedResetFlag(CPED_RESET_FLAG_WasHitByVehicleMelee))
|
||
|
{
|
||
|
bPreferRagdollReaction = true;
|
||
|
}
|
||
|
|
||
|
if( pHitPed->GetUsingRagdoll() || bPreferRagdollReaction )
|
||
|
{
|
||
|
if( CTaskNMBehaviour::CanUseRagdoll( pHitPed, RAGDOLL_TRIGGER_MELEE, pFiringEntity, 0.0f ) )
|
||
|
bUseRagdollReaction = true;
|
||
|
else
|
||
|
bUseBodyIkReaction = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Possible try to find a melee reaction to do.
|
||
|
if( bDoStandardMeleeReactionCheck && uParentMeleeActionResultID != 0 )
|
||
|
{
|
||
|
const CActionDefinition* pForcedMeleeActionToDo = NULL;
|
||
|
if(pMeleeInfo && pMeleeInfo->uForcedReactionDefinitionID != 0)
|
||
|
{
|
||
|
u32 nIndexFound = 0;
|
||
|
pForcedMeleeActionToDo = ACTIONMGR.FindActionDefinition( nIndexFound, pMeleeInfo->uForcedReactionDefinitionID );
|
||
|
}
|
||
|
|
||
|
// Determine if the one receiving the damage is currently blocking.
|
||
|
s32 nCurrentTaskPriority = -1;
|
||
|
CTaskMeleeActionResult* pHitPedCurrentSimpleMeleeActionResultTask = pHitPed->GetPedIntelligence()->GetTaskMeleeActionResult();
|
||
|
if( pHitPedCurrentSimpleMeleeActionResultTask )
|
||
|
{
|
||
|
nCurrentTaskPriority = pHitPedCurrentSimpleMeleeActionResultTask->GetResultPriority();
|
||
|
}
|
||
|
|
||
|
// Check to see if we can find an appropriate hit reaction that is available to do.
|
||
|
CActionFlags::ActionTypeBitSetData actionTypeBitSet;
|
||
|
actionTypeBitSet.Set( CActionFlags::AT_HIT_REACTION, true );
|
||
|
const CActionDefinition* pLocalMeleeActionToDo = ACTIONMGR.SelectSuitableAction(
|
||
|
actionTypeBitSet, // The type we want to search in.
|
||
|
NULL, // No hit combo built up
|
||
|
true, // Test whether or not it is an entry action.
|
||
|
true, // Only find entry actions.
|
||
|
pHitPed,
|
||
|
uParentMeleeActionResultID,
|
||
|
nCurrentTaskPriority,
|
||
|
pFiringPed,
|
||
|
(pHitPedCurrentSimpleMeleeActionResultTask && (pFiringEntity == pHitPedCurrentSimpleMeleeActionResultTask->GetTargetEntity()))?pHitPedCurrentSimpleMeleeActionResultTask->HasLockOnTargetEntity():false,
|
||
|
false,
|
||
|
false,
|
||
|
CActionManager::ShouldDebugPed( pHitPed ) );
|
||
|
|
||
|
//! Clones cannot do ragdoll reactions, so the pForcedMeleeActionToDo will never prefer to ragdoll. Check that we can ragdoll using local reaction. If so, override
|
||
|
//! the network forced reaction.
|
||
|
if(pForcedMeleeActionToDo)
|
||
|
{
|
||
|
if(pLocalMeleeActionToDo && pLocalMeleeActionToDo->PreferRagdollResult() && CTaskNMBehaviour::CanUseRagdoll( pHitPed, RAGDOLL_TRIGGER_MELEE, pFiringEntity, 0.0f ))
|
||
|
{
|
||
|
pMeleeActionToDo = pLocalMeleeActionToDo;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pMeleeActionToDo = pForcedMeleeActionToDo;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pMeleeActionToDo = pLocalMeleeActionToDo;
|
||
|
}
|
||
|
|
||
|
if( pMeleeActionToDo )
|
||
|
{
|
||
|
CTaskMelee::SetLastFoundActionDefinitionForNetworkDamage(pMeleeActionToDo);
|
||
|
|
||
|
// if the attack has killed the ped, use the nm reaction. Animated melee reactions that kill the ped
|
||
|
// (e.g. takedowns and stealth kills) don't go through this path, and the reactions that do result
|
||
|
// in the animated hit reaction being overriden immediately by a relax task.
|
||
|
|
||
|
bool bIsUsingNm = pHitPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_NM_CONTROL );
|
||
|
if(pHitPed->IsNetworkClone() && bIsUsingNm && !pHitPed->GetIsDeadOrDying())
|
||
|
{
|
||
|
bUseBodyIkReaction = true;
|
||
|
}
|
||
|
else if( pHitPed->IsFatallyInjured() || pMeleeActionToDo->PreferRagdollResult() )
|
||
|
{
|
||
|
if( CTaskNMBehaviour::CanUseRagdoll( pHitPed, RAGDOLL_TRIGGER_MELEE, pFiringEntity, 0.0f ) )
|
||
|
bUseRagdollReaction = true;
|
||
|
else
|
||
|
bUseBodyIkReaction = true;
|
||
|
}
|
||
|
else if( pHitPed->GetPedResetFlag( CPED_RESET_FLAG_PreferMeleeBodyIkHitReaction ) || ( pMeleeActionToDo->PreferBodyIkResult() && !pHitPed->GetPedResetFlag( CPED_RESET_FLAG_BlockIkWeaponReactions ) ) )
|
||
|
bUseBodyIkReaction = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bUseBodyIkReaction = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Super hack! The melee system does not play nicely with the damage/death system.
|
||
|
// If doing a ragdoll reaction then we need things to work normally and for the damage event to know that we were injured so that it can wrap our ragdoll task with a CTaskDyingDead task...
|
||
|
// Otherwise, if doing a normal melee reaction we don't want CTaskDyingDead to mess with whatever it is doing so we pretend we weren't injured...
|
||
|
if (!bUseRagdollReaction)
|
||
|
{
|
||
|
tempDamageEvent.GetDamageResponseData().m_bInjured = false;
|
||
|
}
|
||
|
}
|
||
|
// If we plan to kill a ped via melee make sure we do not apply ragdoll yet
|
||
|
else if( !pHitPed->IsDead() &&
|
||
|
( pHitPed->GetPedConfigFlag( CPED_CONFIG_FLAG_KilledByStealth ) ||
|
||
|
pHitPed->GetPedConfigFlag( CPED_CONFIG_FLAG_KilledByTakedown ) ||
|
||
|
pHitPed->GetPedConfigFlag( CPED_CONFIG_FLAG_Knockedout ) ) )
|
||
|
{
|
||
|
return 0.0f;
|
||
|
}
|
||
|
else if(pWeaponInfo->GetDamageType() == DAMAGE_TYPE_EXPLOSIVE || pWeaponInfo->GetDamageType() == DAMAGE_TYPE_FIRE)
|
||
|
{
|
||
|
float fEventScore = CTaskNMBehaviour::WantsToRagdoll(pHitPed, RAGDOLL_TRIGGER_EXPLOSION, pFiringEntity, fForce);
|
||
|
if(fEventScore > 0.0f)
|
||
|
{
|
||
|
if(NetworkInterface::IsGameInProgress())
|
||
|
{
|
||
|
bUseRagdollEvent = true;
|
||
|
}
|
||
|
|
||
|
float fMultiplierScore = CTaskNMBehaviour::CalcRagdollMultiplierScore(pHitPed, RAGDOLL_TRIGGER_EXPLOSION);
|
||
|
if(CTaskNMBehaviour::CanUseRagdoll(pHitPed, RAGDOLL_TRIGGER_EXPLOSION, fEventScore * fMultiplierScore))
|
||
|
{
|
||
|
bUseRagdollReaction = true;
|
||
|
fRagdollImpulseMag = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if(pWeaponInfo->GetDamageType() == DAMAGE_TYPE_ELECTRIC)
|
||
|
{
|
||
|
if(pResult != NULL)
|
||
|
{
|
||
|
// need to calculate our impulse and force values here before we update the shot task below
|
||
|
vRagdollImpulseDir = pResult->GetHitPosition() - vStart;
|
||
|
float fDistance = NormalizeAndMag(vRagdollImpulseDir);
|
||
|
|
||
|
bool bFront = true;
|
||
|
// Determine whether this is a front or back shot
|
||
|
if(weaponVerifyf(pHitPed->GetRagdollInst(), "pHitPed [0x%p] has no RagdollInst", pHitPed))
|
||
|
{
|
||
|
const fragInstNMGta* pRagdollInst = pHitPed->GetRagdollInst();
|
||
|
if(weaponVerifyf(pRagdollInst->GetCacheEntry(), "pHitPed [0x%p]: RagdollInst [0x%p]: No Cache Entry", pHitPed, pRagdollInst))
|
||
|
{
|
||
|
const fragCacheEntry* pCacheEntry = pRagdollInst->GetCacheEntry();
|
||
|
if(weaponVerifyf(pCacheEntry->GetBound(), "pHitPed [0x%p]: RagdollInst [0x%p]: CacheEntry [0x%p]: No Bound", pHitPed, pRagdollInst, pCacheEntry))
|
||
|
{
|
||
|
const phBoundComposite* pBound = pCacheEntry->GetBound();
|
||
|
if(weaponVerifyf(iHitComponent >= 0 && iHitComponent < pBound->GetMaxNumBounds(), "pHitPed [0x%p]: RagdollInst [0x%p]: CacheEntry [0x%p]: Bound [0x%p]: iHitComponent [%d] out of range [0..%d]", pHitPed, pRagdollInst, pCacheEntry, pBound, iHitComponent, pBound->GetMaxNumBounds()))
|
||
|
{
|
||
|
Mat34V xComponentMat;
|
||
|
Transform(xComponentMat, pRagdollInst->GetMatrix(), pBound->GetCurrentMatrix(iHitComponent));
|
||
|
|
||
|
Vec3V vDirFacing = -xComponentMat.GetCol2();
|
||
|
vDirFacing.SetZ(ScalarV(V_ZERO));
|
||
|
vDirFacing = NormalizeSafe(vDirFacing, Vec3V(V_X_AXIS_WZERO));
|
||
|
bFront = Dot(vDirFacing, VECTOR3_TO_VEC3V(vRagdollImpulseDir)).Getf() <= 0.0f;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
eAnimBoneTag iHitBoneTag = BONETAG_ROOT;
|
||
|
if(iHitComponent > -1)
|
||
|
{
|
||
|
iHitBoneTag = pHitPed->GetBoneTagFromRagdollComponent(iHitComponent);
|
||
|
}
|
||
|
fRagdollImpulseMag = pWeaponInfo->GetForceHitPed(iHitBoneTag, bFront, fDistance);
|
||
|
}
|
||
|
|
||
|
if( pHitPed->GetPedConfigFlag( CPED_CONFIG_FLAG_IsSwimming ) )
|
||
|
{
|
||
|
bTriggerDrowning = true;
|
||
|
}
|
||
|
}
|
||
|
else if(pWeaponInfo->GetIsGun() && pWeaponInfo->GetDamageType() != DAMAGE_TYPE_TRANQUILIZER)
|
||
|
{
|
||
|
if(pHitPed->IsLocalPlayer() && pFiringEntity)
|
||
|
{
|
||
|
if(pFiringEntity->GetIsTypePed() && ((CPed*)pFiringEntity)->GetWeaponManager() && ((CPed*)pFiringEntity)->GetWeaponManager()->GetEquippedWeapon())
|
||
|
{
|
||
|
if (((CPed*)pFiringEntity)->GetWeaponManager()->GetEquippedWeapon())
|
||
|
{
|
||
|
((CPed*)pFiringEntity)->GetWeaponManager()->GetEquippedWeapon()->GetAudioComponent().HandleHitPlayer();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
eRagdollTriggerTypes nTrigger = pWeaponInfo->GetDamageType() != DAMAGE_TYPE_BULLET_RUBBER ? RAGDOLL_TRIGGER_BULLET : RAGDOLL_TRIGGER_RUBBERBULLET;
|
||
|
if((nTrigger == RAGDOLL_TRIGGER_BULLET || (!pHitPed->IsAPlayerPed() || (tempDamageEvent.GetDamageResponseData().m_bHeadShot || tempDamageEvent.GetDamageResponseData().m_bHitTorso))) && !tempDamageEvent.GetDamageResponseData().m_bIncapacitated)
|
||
|
{
|
||
|
float fEventScore = pResult != NULL ? CTaskNMBehaviour::WantsToRagdoll(pHitPed, nTrigger, pFiringEntity, fForce) : 0.0f;
|
||
|
if(fEventScore > 0.0f)
|
||
|
{
|
||
|
if(NetworkInterface::IsGameInProgress())
|
||
|
{
|
||
|
bUseRagdollEvent = true;
|
||
|
}
|
||
|
float fMultiplierScore = CTaskNMBehaviour::CalcRagdollMultiplierScore(pHitPed, nTrigger);
|
||
|
if(CTaskNMBehaviour::CanUseRagdoll(pHitPed, nTrigger, fEventScore * fMultiplierScore))
|
||
|
{
|
||
|
bUseRagdollReaction = true;
|
||
|
|
||
|
fragInstNMGta* pRagdollInst = pHitPed->GetRagdollInst();
|
||
|
|
||
|
// Calculate force now
|
||
|
vRagdollImpulseDir = pResult->GetHitPosition() - vStart;
|
||
|
float fDistance = NormalizeAndMag(vRagdollImpulseDir);
|
||
|
|
||
|
// Don't want to just push ped into the ground all the time
|
||
|
static dev_bool bModifyShootDir = false;
|
||
|
f32 fHorizontalMag = vRagdollImpulseDir.XYMag();
|
||
|
if(bModifyShootDir && fHorizontalMag > 0.05f)
|
||
|
{
|
||
|
vRagdollImpulseDir.x /= fHorizontalMag;
|
||
|
vRagdollImpulseDir.y /= fHorizontalMag;
|
||
|
}
|
||
|
|
||
|
weaponAssertf(vRagdollImpulseDir.FiniteElements(), "CWeaponDamage::GeneratePedDamageEvent - Error: vRagdollImpulseDir.FiniteElements(). X:%.3f, Y:%.3f, Z:%.3f", vRagdollImpulseDir.x, vRagdollImpulseDir.y, vRagdollImpulseDir.z);
|
||
|
|
||
|
s32 iHitComponent = pResult->GetHitComponent();
|
||
|
eAnimBoneTag iHitBoneTag = BONETAG_ROOT;
|
||
|
|
||
|
Mat34V xComponentMat(pRagdollInst->GetMatrix());
|
||
|
|
||
|
if(iHitComponent >= 0 && pRagdollInst->GetCached())
|
||
|
{
|
||
|
iHitBoneTag = pHitPed->GetBoneTagFromRagdollComponent(iHitComponent);
|
||
|
|
||
|
phArchetype* pArchetype = pRagdollInst->GetArchetype();
|
||
|
if(nmVerifyf(pArchetype != NULL, "CWeaponDamage::GeneratePedDamageEvent: Invalid archetype") &&
|
||
|
nmVerifyf(pArchetype->GetBound() != NULL && pArchetype->GetBound()->GetType() == phBound::COMPOSITE, "CWeaponDamage::GeneratePedDamageEvent: Invalid bound"))
|
||
|
{
|
||
|
int iMappedComponent = pRagdollInst->MapRagdollLODComponentHighToCurrent(iHitComponent);
|
||
|
if(nmVerifyf(iMappedComponent != -1 && iMappedComponent < static_cast<phBoundComposite*>(pArchetype->GetBound())->GetNumBounds(), "CWeaponDamage::GeneratePedDamageEvent: Ped (%s). Mapped Ragdoll component [%d] is out of range 0..[%d]. Original component [%d]", pHitPed->GetModelName(), iMappedComponent, pRagdollInst->GetTypePhysics()->GetNumChildren() - 1, iHitComponent))
|
||
|
{
|
||
|
Transform(xComponentMat, static_cast<phBoundComposite*>(pArchetype->GetBound())->GetCurrentMatrix(iMappedComponent));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Vec3V vDirFacing = -xComponentMat.GetCol2();
|
||
|
vDirFacing.SetZ(ScalarV(V_ZERO));
|
||
|
vDirFacing = NormalizeSafe(vDirFacing, Vec3V(V_X_AXIS_WZERO));
|
||
|
// Determine whether this is a front or back shot
|
||
|
bool bFront = Dot(vDirFacing, VECTOR3_TO_VEC3V(vRagdollImpulseDir)).Getf() <= 0.0f;
|
||
|
|
||
|
fRagdollImpulseMag = pWeaponInfo->GetForceHitPed(iHitBoneTag, bFront, fDistance);
|
||
|
|
||
|
// If script wants to force their control of the ragdoll, only apply minimal forces to the ragdoll
|
||
|
static dev_float sfScriptControlledRagdollMult = 0.1f;
|
||
|
static dev_float sfRagdollBulletForceMult = 1.0f;
|
||
|
fRagdollImpulseMag *= pHitPed->GetPedResetFlag( CPED_RESET_FLAG_ForceScriptControlledRagdoll ) ? sfScriptControlledRagdollMult : sfRagdollBulletForceMult;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check to see if ped was hit by rubber bullet
|
||
|
if( pWeaponInfo->GetDamageType()==DAMAGE_TYPE_BULLET_RUBBER )
|
||
|
{
|
||
|
if( pHitPed->GetPedConfigFlag( CPED_CONFIG_FLAG_IsSwimming ) )
|
||
|
{
|
||
|
bTriggerDrowning = true;
|
||
|
}
|
||
|
else if( pHitPed->GetPedResetFlag( CPED_RESET_FLAG_IsClimbing ) && CTaskNMBehaviour::CanUseRagdoll( pHitPed, RAGDOLL_TRIGGER_FALL, pFiringEntity, fForce ) )
|
||
|
{
|
||
|
bUseRagdollReaction = true;
|
||
|
fRagdollImpulseMag = fForce;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if(uWeaponHash == WEAPONTYPE_DROWNING)
|
||
|
{
|
||
|
eRagdollTriggerTypes trigger = RAGDOLL_TRIGGER_IN_WATER;
|
||
|
if(CTaskNMBehaviour::CanUseRagdoll(pHitPed, trigger, pFiringEntity, fForce))
|
||
|
{
|
||
|
bUseRagdollReaction = true;
|
||
|
fRagdollImpulseMag = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
// Falling damage events can cause ragdoll reactions - this is used to generically damage peds without using guns
|
||
|
// but causing ragdoll reactions
|
||
|
else if(uWeaponHash == WEAPONTYPE_FALL)
|
||
|
{
|
||
|
if(CTaskNMBehaviour::CanUseRagdoll(pHitPed, RAGDOLL_TRIGGER_FALL))
|
||
|
{
|
||
|
bUseRagdollReaction = true;
|
||
|
fRagdollImpulseMag = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
else if((uWeaponHash == WEAPONTYPE_SMOKEGRENADE || uWeaponHash == WEAPONTYPE_DLC_BOMB_GAS || uWeaponHash == WEAPONTYPE_DLC_BZGAS_MK2) &&
|
||
|
(pHitPed->HasHurtStarted() || pHitPed->GetPedConfigFlag(CPED_CONFIG_FLAG_CowerInsteadOfFlee)))
|
||
|
{
|
||
|
bool bBlockRagdoll = false;
|
||
|
if(pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pFiringPed = (CPed*)pFiringEntity;
|
||
|
bBlockRagdoll = pHitPed->GetPedIntelligence()->IsFriendlyWith(*pFiringPed);
|
||
|
}
|
||
|
|
||
|
if(!bBlockRagdoll && CTaskNMBehaviour::CanUseRagdoll(pHitPed, RAGDOLL_TRIGGER_SMOKE_GRENADE))
|
||
|
{
|
||
|
bUseRagdollReaction = true;
|
||
|
fRagdollImpulseMag = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
else if(uWeaponHash == WEAPONTYPE_ROTORS )
|
||
|
{
|
||
|
if(CTaskNMBehaviour::CanUseRagdoll(pHitPed, RAGDOLL_TRIGGER_HELIBLADES))
|
||
|
{
|
||
|
bUseRagdollReaction = true;
|
||
|
}
|
||
|
}
|
||
|
else if (uWeaponHash == WEAPONTYPE_DLC_SNOWBALL)
|
||
|
{
|
||
|
if (pResult)
|
||
|
{
|
||
|
bool bRagdollPermitted = false;
|
||
|
|
||
|
vRagdollImpulseDir = -pResult->GetHitNormal();
|
||
|
|
||
|
// Ragdoll only if hit in the head and not getting up (unless we've killed the ped)
|
||
|
bRagdollPermitted = ((iHitBoneTag==BONETAG_HEAD || iHitBoneTag==BONETAG_NECK) && !pHitPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_GET_UP))
|
||
|
|| pHitPed->ShouldBeDead()
|
||
|
|| (pHitPed->IsNetworkClone() && (fWeaponDamage >= (pHitPed->GetHealth() - pHitPed->GetInjuredHealthThreshold())));
|
||
|
|
||
|
if (bRagdollPermitted && CTaskNMBehaviour::CanUseRagdoll(pHitPed, RAGDOLL_TRIGGER_SNOWBALL, pFiringEntity, 0.0f))
|
||
|
{
|
||
|
bUseRagdollReaction = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bUseBodyIkReaction = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bTriggerDrowning)
|
||
|
{
|
||
|
// Force the ped to start drowning (take incremental damage)
|
||
|
Assert( pHitPed->GetPedIntelligence() );
|
||
|
Assert( pHitPed->GetPedIntelligence()->GetEventScanner() );
|
||
|
pHitPed->GetPedIntelligence()->GetEventScanner()->GetInWaterEventScanner().StartDrowningNow( *pHitPed );
|
||
|
}
|
||
|
|
||
|
// Handle damage to peds that are already dead.
|
||
|
if(pHitPed->IsDead())
|
||
|
{
|
||
|
bool bCanQuitWhenDead = true;
|
||
|
CTask* pTaskActive = pHitPed->GetPedIntelligence()->GetTaskActive();
|
||
|
CDamageInfo damageInfo(uWeaponHash, &vStart, pResult, &vRagdollImpulseDir, fRagdollImpulseMag, pHitPed);
|
||
|
CTaskNMBehaviour* pTaskNM = pHitPed->GetPedIntelligence()->GetLowestLevelNMTask(pHitPed);
|
||
|
|
||
|
if(pResult)
|
||
|
{
|
||
|
if(pTaskNM != NULL && pTaskNM->GetTaskType() == CTaskTypes::TASK_NM_SHOT)
|
||
|
{
|
||
|
static_cast<CTaskNMShot*>(pTaskNM)->UpdateShot(pHitPed, pFiringEntity, uWeaponHash, pResult->GetHitComponent(), pResult->GetHitPosition(), fRagdollImpulseMag * vRagdollImpulseDir, pResult->GetHitNormal());
|
||
|
}
|
||
|
else if(pTaskNM != NULL && pTaskNM->GetTaskType() == CTaskTypes::TASK_NM_FLINCH)
|
||
|
{
|
||
|
Vector3 vHitNormal;
|
||
|
vHitNormal.Negate(pResult->GetHitNormal());
|
||
|
taskDisplayf("melee hit: position (x:%.3f, y:%.3f, z:%.3f) Normal (x:%.3f, y:%.3f, z:%.3f) ragdollImpulseDir (x:%.3f, y:%.3f, z:%.3f)"
|
||
|
, pResult->GetHitPosition().x, pResult->GetHitPosition().y, pResult->GetHitPosition().z
|
||
|
, vHitNormal.x, vHitNormal.y, vHitNormal.z
|
||
|
, vRagdollImpulseDir.x, vRagdollImpulseDir.y, vRagdollImpulseDir.z);
|
||
|
|
||
|
static_cast<CTaskNMFlinch*>(pTaskNM)->UpdateFlinch(pHitPed, pFiringEntity != NULL ? VEC3V_TO_VECTOR3(pFiringEntity->GetTransform().GetPosition()) : VEC3_ZERO,
|
||
|
pFiringEntity, CTaskNMFlinch::FLINCHTYPE_MELEE, uWeaponHash, (s32)pResult->GetHitComponent(), false, &pResult->GetHitPosition(), &vHitNormal, &vRagdollImpulseDir);
|
||
|
|
||
|
if (CTaskNMBehaviour::ms_bUseParameterSets && !pHitPed->CheckAgilityFlags(AF_DONT_FLINCH_ON_MELEE))
|
||
|
{
|
||
|
static_cast<CTaskNMFlinch*>(pTaskNM)->SetType(CTaskNMFlinch::FLINCHTYPE_MELEE_PASSIVE);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
static_cast<CTaskNMFlinch*>(pTaskNM)->SetType(CTaskNMFlinch::FLINCHTYPE_MELEE);
|
||
|
}
|
||
|
}
|
||
|
else if(pTaskActive && pTaskActive->RecordDamage(damageInfo))
|
||
|
{
|
||
|
// Do nothing, the corpse task has handled the reaction
|
||
|
// But try to use an IK react if the ped is in a vehicle
|
||
|
if (!bUseRagdollReaction && !pHitPed->GetUsingRagdoll() && (pHitPed->GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle) || pHitPed->GetPedConfigFlag(CPED_CONFIG_FLAG_OnMount)))
|
||
|
{
|
||
|
if (!pHitPed->GetPedResetFlag(CPED_RESET_FLAG_BlockIkWeaponReactions))
|
||
|
{
|
||
|
Vec3V vPosition(VECTOR3_TO_VEC3V(pResult->GetHitPosition()));
|
||
|
Vec3V vDirection(NormalizeFast(Subtract(vPosition, VECTOR3_TO_VEC3V(vStart))));
|
||
|
|
||
|
CIkRequestBodyReact bodyReactRequest(vPosition, vDirection, (int)pResult->GetHitComponent());
|
||
|
bodyReactRequest.SetWeaponGroup(pWeaponInfo->GetGroup());
|
||
|
pHitPed->GetIkManager().Request(bodyReactRequest);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Apply force using a bullet force helper, if one is available
|
||
|
if(pHitPed->GetUsingRagdoll() && bUseRagdollReaction && fRagdollImpulseMag > 0.0f)
|
||
|
{
|
||
|
pHitPed->GetRagdollInst()->ApplyBulletForce(pHitPed, fRagdollImpulseMag, vRagdollImpulseDir, pResult->GetHitPosition(), pResult->GetHitComponent(), uWeaponHash);
|
||
|
}
|
||
|
|
||
|
// Ragdoll is being used, but no task is controlling it, so can't quit out here
|
||
|
if(pHitPed->GetRagdollState() >= RAGDOLL_STATE_PHYS_ACTIVATE)
|
||
|
{
|
||
|
bCanQuitWhenDead = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(modifiedFlags.IsFlagSet(CPedDamageCalculator::DF_MeleeDamage) && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CCrime::ReportCrime(CRIME_HIT_PED, pHitPed, static_cast<CPed*>(pFiringEntity));
|
||
|
}
|
||
|
|
||
|
// The damage has been handled so we can quick exit here.
|
||
|
if(bCanQuitWhenDead)
|
||
|
{
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}// END handle damage to peds that are already dead.
|
||
|
|
||
|
// Allow for post death melee ragdoll reactions
|
||
|
if(!modifiedFlags.IsFlagSet(CPedDamageCalculator::DF_MeleeDamage) && !tempDamageEvent.AffectsPed(pHitPed))
|
||
|
{
|
||
|
// It doesn't need to do anything else to the ped.
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
//! DMKH. Removed old legacy code. We need to kick off hit reactions on hit ped owner's machine, so skip this code now.
|
||
|
|
||
|
// In network games melee hit reaction animations are handled separately from when the damage
|
||
|
// is applied from the ped. The hit reactions are played immediately to reduce the effects of
|
||
|
// network latency, but the damage is applied when the network message is received from the owner.
|
||
|
// When applying damage we don't want the damage event to affect the ped in other ways since the
|
||
|
// hit reaction will already have been invoked.
|
||
|
/*bool bHandlingNetworkMeleeHealthChangesLocally = !bAllowMeleeHitReactions && (bIsMeleeDamage || pWeaponInfo->GetIsMelee());
|
||
|
if(bHandlingNetworkMeleeHealthChangesLocally)
|
||
|
{
|
||
|
return (tempDamageEvent.GetDamageResponseData().m_fHealthLost + tempDamageEvent.GetDamageResponseData().m_fArmourLost);
|
||
|
}*/
|
||
|
|
||
|
// The damage event needs to affect the ped in other ways...
|
||
|
bool bWasKilled = tempDamageEvent.GetDamageResponseData().m_bKilled;
|
||
|
bool bWasKilledOrInjured = (bWasKilled || tempDamageEvent.GetDamageResponseData().m_bInjured);
|
||
|
fwMvClipId clipId = CLIP_ID_INVALID;
|
||
|
fwMvClipSetId clipSetId = CLIP_SET_ID_INVALID;
|
||
|
CTask* pTaskPhysicalResponse = NULL;
|
||
|
|
||
|
// It is ragdoll damage.
|
||
|
#if ENABLE_HORSE
|
||
|
if (!bWasKilled && pHitPed->GetHorseComponent()) //mountable animals don't ragdoll until dead (TODO maybe all animals?)
|
||
|
{
|
||
|
bUseRagdollReaction = false;
|
||
|
bUseRagdollEvent = false;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
bool bUpdatedPreviousEvent = false;
|
||
|
if( bUseRagdollReaction || bUseRagdollEvent )
|
||
|
{
|
||
|
bool bTaskAppliesImpulse = false;
|
||
|
pTaskPhysicalResponse = GenerateRagdollTask(pFiringEntity, pHitPed, uWeaponHash, fWeaponDamage, modifiedFlags, bWasKilledOrInjured, vStart, pResult, vRagdollImpulseDir, fRagdollImpulseMag, bTaskAppliesImpulse, bUpdatedPreviousEvent);
|
||
|
|
||
|
pHitPed->SetClothPinRadiusScale(0.0f);
|
||
|
|
||
|
// Force should have already been scaled by component mass by this point
|
||
|
if(pHitPed->GetUsingRagdoll() && fRagdollImpulseMag > 0.0f && pResult)
|
||
|
{
|
||
|
// Apply force using a bullet force helper, if one is available
|
||
|
if (!bTaskAppliesImpulse || pHitPed->GetRagdollInst()->m_AgentId == -1)
|
||
|
pHitPed->GetRagdollInst()->ApplyBulletForce(pHitPed, fRagdollImpulseMag, vRagdollImpulseDir, pResult->GetHitPosition(), pResult->GetHitComponent(), uWeaponHash);
|
||
|
|
||
|
// If any additional force has been specified, apply it now
|
||
|
if(!vAdditionalRagdollForce.IsZero())
|
||
|
{
|
||
|
s32 iHitComponent = pHitPed->GetRagdollComponentFromBoneTag(BONETAG_SPINE2);
|
||
|
pHitPed->ApplyImpulse(vAdditionalRagdollForce, VEC3_ZERO, iHitComponent, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// store the damage information for applying the damage reaction later, this is required so we can kick off local ragdoll reactions for dying peds
|
||
|
if (pResult && pHitPed->IsAPlayerPed() && pHitPed->GetNetworkObject())
|
||
|
{
|
||
|
static_cast<CNetObjPlayer*>(pHitPed->GetNetworkObject())->SetLastDamageInfo(pResult->GetHitPosition(), vRagdollImpulseDir, pResult->GetHitComponent());
|
||
|
}
|
||
|
}
|
||
|
else if( bUseBodyIkReaction && pResult )
|
||
|
{
|
||
|
CIkRequestBodyReact bodyReactRequest(RCC_VEC3V(pResult->GetHitPosition()), RC_VEC3V(vRagdollImpulseDir), (int)pResult->GetHitComponent());
|
||
|
bodyReactRequest.SetWeaponGroup(pWeaponInfo->GetGroup());
|
||
|
pHitPed->GetIkManager().Request(bodyReactRequest);
|
||
|
}
|
||
|
else if( bIsStandardMeleeReaction )
|
||
|
{
|
||
|
// It is melee damage
|
||
|
|
||
|
// Determine if the ped is already doing the correct reaction.
|
||
|
// This can happen when the reaction anim itself is invoking the damage.
|
||
|
if(!modifiedFlags.IsFlagSet(CPedDamageCalculator::DF_MeleeDamage))
|
||
|
{
|
||
|
// Check if it is injured/die damage and if this melee self damage event
|
||
|
// killed the ped.
|
||
|
// If it isn't then we don't need to do anything since we are currently
|
||
|
// playing the correct animation, so just let it play out and be removed.
|
||
|
// Otherwise we are already doing the correct animation, but we need to
|
||
|
// make sure it isn't removed (since we are dead and don't need to do any
|
||
|
// other anims).
|
||
|
if( bWasKilled )
|
||
|
{
|
||
|
// Make sure the health is set to be totally dead.
|
||
|
pHitPed->SetHealth(0.0f);
|
||
|
}
|
||
|
|
||
|
// Make sure we don't do anything (see above).
|
||
|
bAllowTempDamageEventToBeAdded = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Check if it is injured/die damage.
|
||
|
if( bWasKilledOrInjured )
|
||
|
{
|
||
|
// Make a injured/die task.
|
||
|
CDeathSourceInfo info(pFiringEntity, uWeaponHash, fHitDir, iHitBoneTag);
|
||
|
|
||
|
const CActionResult* pActionResult = pMeleeActionToDo ? pMeleeActionToDo->GetActionResult( pHitPed ) : NULL;
|
||
|
if( pActionResult && pActionResult->GetClipSetID() != CLIP_SET_ID_INVALID && pActionResult->GetAnimID() != CLIP_ID_INVALID )
|
||
|
{
|
||
|
pTaskPhysicalResponse = rage_new CTaskDyingDead(&info);
|
||
|
static_cast<CTaskDyingDead*>(pTaskPhysicalResponse)->SetDeathAnimationBySet( fwMvClipSetId(pActionResult->GetClipSetID()), fwMvClipId(pActionResult->GetAnimID()), NORMAL_BLEND_DURATION);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
CTaskMeleeActionResult* pHitPedMeleeActionTask = pHitPed->GetPedIntelligence()->GetTaskMeleeActionResult();
|
||
|
if( pHitPedMeleeActionTask && pHitPedMeleeActionTask->GetActionResult() && !pHitPedMeleeActionTask->IsOffensiveMove() )
|
||
|
{
|
||
|
pTaskPhysicalResponse = rage_new CTaskDyingDead(&info, CTaskDyingDead::Flag_startDead);
|
||
|
static_cast<CTaskDyingDead*>(pTaskPhysicalResponse)->SetDeathAnimationBySet( fwMvClipSetId( pHitPedMeleeActionTask->GetActionResult()->GetClipSetID() ), fwMvClipId( pHitPedMeleeActionTask->GetActionResult()->GetAnimID() ), INSTANT_BLEND_DURATION);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pTaskPhysicalResponse = rage_new CTaskDyingDead(&info);
|
||
|
}
|
||
|
|
||
|
// Force this task to activate on this frame otherwise there is a 1 frame CTaskDoNothing frame that attemps to blend into the death animation
|
||
|
pHitPed->SetPedResetFlag( CPED_RESET_FLAG_ForcePostCameraAIUpdate, true );
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Try to get a pointer to the actionResults via the actionResult Id for the action.
|
||
|
const CActionResult* pActionResult = pMeleeActionToDo ? pMeleeActionToDo->GetActionResult( pHitPed ) : NULL;
|
||
|
if( pActionResult )
|
||
|
{
|
||
|
CTaskMelee* pHitPedComplexActionTask = pHitPed->GetPedIntelligence()->GetTaskMelee();
|
||
|
if( pHitPedComplexActionTask )
|
||
|
{
|
||
|
pHitPedComplexActionTask->SetLocalReaction( pActionResult, true, pMeleeInfo ? pMeleeInfo->uNetworkActionID : 0 );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bool bCanAddMeleeReactionTask = true;
|
||
|
if (pHitPed->GetPedResetFlag(CPED_RESET_FLAG_IsEnteringVehicle) && pHitPed->GetIsAttached())
|
||
|
{
|
||
|
const CTaskEnterVehicle* pEnterVehicleTask = static_cast<const CTaskEnterVehicle*>(pHitPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_ENTER_VEHICLE));
|
||
|
if (pEnterVehicleTask)
|
||
|
{
|
||
|
const aiTask* pSubTask = pEnterVehicleTask->GetSubTask();
|
||
|
const s32 iState = pEnterVehicleTask->GetState();
|
||
|
if (iState < CTaskEnterVehicle::State_OpenDoor)
|
||
|
{
|
||
|
bCanAddMeleeReactionTask = true;
|
||
|
}
|
||
|
else if (iState == CTaskEnterVehicle::State_OpenDoor && pSubTask && pSubTask->GetTaskType() == CTaskTypes::TASK_OPEN_VEHICLE_DOOR_FROM_OUTSIDE)
|
||
|
{
|
||
|
//! Don't do local reaction here. If the owner has proceeded past this point & we attempt to locally react ped, they'll snap into vehicle with a pretty
|
||
|
//! bad blend.
|
||
|
if(pHitPed->IsNetworkClone())
|
||
|
{
|
||
|
bCanAddMeleeReactionTask = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bCanAddMeleeReactionTask = (pSubTask->GetState() == CTaskOpenVehicleDoorFromOutside::State_OpenDoor
|
||
|
|| pSubTask->GetState() == CTaskOpenVehicleDoorFromOutside::State_OpenDoorBlend
|
||
|
|| pSubTask->GetState() == CTaskOpenVehicleDoorFromOutside::State_OpenDoorCombat
|
||
|
|| pSubTask->GetState() == CTaskOpenVehicleDoorFromOutside::State_OpenDoorWater);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bCanAddMeleeReactionTask = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(pHitPed->IsNetworkClone() && pHitPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning( CTaskTypes::TASK_VAULT, false ))
|
||
|
{
|
||
|
bCanAddMeleeReactionTask = false;
|
||
|
}
|
||
|
|
||
|
// B*2001652: Don't add melee reaction task if we're running the NM task and don't have our balance.
|
||
|
if (pHitPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_NM_CONTROL))
|
||
|
{
|
||
|
CTaskNMControl* pNMTask = smart_cast<CTaskNMControl*>(pHitPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_NM_CONTROL));
|
||
|
if(pNMTask && pNMTask->IsFeedbackFlagSet(CTaskNMControl::BALANCE_FAILURE))
|
||
|
{
|
||
|
bCanAddMeleeReactionTask = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bCanAddMeleeReactionTask)
|
||
|
{
|
||
|
// Add a new simple action.
|
||
|
weaponAssert( pFiringEntity && pFiringEntity->GetIsTypePed() );
|
||
|
pTaskPhysicalResponse = rage_new CTaskMelee( pActionResult, static_cast<CPed*>( pFiringEntity ), CTaskMelee::MF_IsDoingReaction, CSimpleImpulseTest::ImpulseNone, 0, pMeleeInfo ? pMeleeInfo->uNetworkActionID : 0 );
|
||
|
tempDamageEvent.SetMeleeResponse(true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Force update the AI state to save a frame when reaction to the strike
|
||
|
pHitPed->SetPedResetFlag( CPED_RESET_FLAG_ForcePostCameraAIUpdate, true );
|
||
|
|
||
|
static dev_bool s_bInstantUpdateForClone = true;
|
||
|
if(pHitPed->IsNetworkClone() && s_bInstantUpdateForClone)
|
||
|
{
|
||
|
pHitPed->SetPedResetFlag( CPED_RESET_FLAG_AllowCloneForcePostCameraAIUpdate, true );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if(pWeaponInfo->GetDamageType()==DAMAGE_TYPE_FIRE)
|
||
|
{
|
||
|
CTask* pCurrentTask = pHitPed->GetPedIntelligence()->GetTaskActive();
|
||
|
if(pCurrentTask->GetTaskType() != CTaskTypes::TASK_COMPLEX_ON_FIRE)
|
||
|
{
|
||
|
pTaskPhysicalResponse = rage_new CTaskComplexOnFire(uWeaponHash);
|
||
|
}
|
||
|
}
|
||
|
else if( !pHitPed->GetTaskData().GetIsFlagSet(CTaskFlags::DisableNonStandardDamageTypes) &&
|
||
|
( (pWeaponInfo->GetDamageType() == DAMAGE_TYPE_ELECTRIC && !pHitPed->GetPedConfigFlag(CPED_CONFIG_FLAG_BlockElectricWeaponDamage)) ||
|
||
|
(pWeaponInfo->GetDamageType() == DAMAGE_TYPE_BULLET_RUBBER && (!pHitPed->IsAPlayerPed() || tempDamageEvent.GetDamageResponseData().m_bHeadShot || tempDamageEvent.GetDamageResponseData().m_bHitTorso)) ) )
|
||
|
{
|
||
|
// Calculate damage response length
|
||
|
u32 uDamageTime;
|
||
|
if (pHitPed->GetMinOnGroundTimeForStunGun()>=0)
|
||
|
{
|
||
|
TUNE_GROUP_FLOAT(STUNGUN, fFallDownTimeForTimeOverride, 2000.0f, 0.0f, 1000000.0f, 50.0f);
|
||
|
uDamageTime = (u32)fFallDownTimeForTimeOverride + (u32)pHitPed->GetMinOnGroundTimeForStunGun();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(pHitPed->GetVehiclePedInside())
|
||
|
{
|
||
|
uDamageTime = static_cast<u32>((tempDamageEvent.GetDamageResponseData().m_bHeadShot ? pWeaponInfo->GetDamageTimeInVehicleHeadShot() : pWeaponInfo->GetDamageTimeInVehicle())*1000.0f);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
uDamageTime = static_cast<u32>(pWeaponInfo->GetDamageTime()*1000.0f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (pHitPed->IsPlayer() && pWeaponInfo->GetBlockRagdollResponseTaskForPlayer())
|
||
|
{
|
||
|
// Do nothing
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Create ragdoll response task for given length
|
||
|
CTask* pCurrentTask = pHitPed->GetPedIntelligence()->GetTaskActive();
|
||
|
if(pCurrentTask->GetTaskType() != CTaskTypes::TASK_DAMAGE_ELECTRIC)
|
||
|
{
|
||
|
CPed *pFiringEntityPed = NULL;
|
||
|
if (pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
pFiringEntityPed = static_cast<CPed *>(pFiringEntity);
|
||
|
|
||
|
eRagdollTriggerTypes ragdollTriggerType = GetRagdollTriggerForDamageType(pWeaponInfo->GetDamageType());
|
||
|
|
||
|
if (pResult)
|
||
|
{
|
||
|
vRagdollImpulseDir = pResult->GetHitPosition() - vStart;
|
||
|
vRagdollImpulseDir.Normalize();
|
||
|
|
||
|
// Apply a stun gun impulse here when dangling (otherwise the electric task is getting killed in that case with no impulse applied)
|
||
|
CTask* pTaskSimplest = pHitPed->GetPedIntelligence()->GetTaskActiveSimplest();
|
||
|
if (pTaskSimplest->GetTaskType() == CTaskTypes::TASK_NM_DANGLE)
|
||
|
pHitPed->GetRagdollInst()->ApplyBulletForce(pHitPed, fRagdollImpulseMag, vRagdollImpulseDir, pResult->GetHitPosition(), pResult->GetHitComponent(), uWeaponHash);
|
||
|
|
||
|
pTaskPhysicalResponse = rage_new CTaskDamageElectric(pWeaponInfo->GetPedMotionClipSetId(), uDamageTime, ragdollTriggerType,
|
||
|
pFiringEntity, uWeaponHash, pResult->GetHitComponent(), pResult->GetHitPosition(), fRagdollImpulseMag * vRagdollImpulseDir, pResult->GetHitNormal());
|
||
|
|
||
|
if(pTaskPhysicalResponse && uWeaponHash == atHashString("WEAPON_STUNGUN_MP", 0x45CD9CF3))
|
||
|
{
|
||
|
pHitPed->SetPedConfigFlag(CPED_CONFIG_FLAG_DisableHealthRegenerationWhenStunned, true);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pTaskPhysicalResponse = rage_new CTaskDamageElectric(pWeaponInfo->GetPedMotionClipSetId(), uDamageTime, ragdollTriggerType);
|
||
|
((CTaskDamageElectric*)pTaskPhysicalResponse)->SetContactPosition(vStart);
|
||
|
}
|
||
|
|
||
|
// Raise this to a ragdoll event if possible (otherwise it won't activate immediately if the ped is being bumped, etc)
|
||
|
if (pHitPed->GetUsingRagdoll() || CTaskNMBehaviour::CanUseRagdoll(pHitPed, ragdollTriggerType, pFiringEntity))
|
||
|
{
|
||
|
bUseRagdollReaction = true;
|
||
|
}
|
||
|
}
|
||
|
else if (pResult)
|
||
|
{
|
||
|
vRagdollImpulseDir = pResult->GetHitPosition() - vStart;
|
||
|
vRagdollImpulseDir.Normalize();
|
||
|
CTaskDamageElectric* pDamageElectricTask = static_cast<CTaskDamageElectric*>(pCurrentTask);
|
||
|
pDamageElectricTask->Update(uDamageTime, pFiringEntity, uWeaponHash, pResult->GetHitComponent(), pResult->GetHitPosition(), fRagdollImpulseMag * vRagdollImpulseDir, pResult->GetHitNormal());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// It is normal (non-ragdoll, non-melee) damage.
|
||
|
// Check if it is injured/die damage.
|
||
|
if( bWasKilledOrInjured )
|
||
|
{
|
||
|
// give the player a die by smoke grenade nm reaction
|
||
|
const bool bGasWeapon = uWeaponHash == WEAPONTYPE_SMOKEGRENADE || uWeaponHash == WEAPONTYPE_DLC_BOMB_GAS || uWeaponHash == WEAPONTYPE_DLC_BZGAS_MK2;
|
||
|
if (bGasWeapon && (pHitPed->IsLocalPlayer() || pHitPed->EstimatePose()!=POSE_STANDING) && CTaskNMBehaviour::CanUseRagdoll(pHitPed, RAGDOLL_TRIGGER_SMOKE_GRENADE))
|
||
|
{
|
||
|
const CTaskNMSimple::Tunables::Tuning* pTuning = CTaskNMSimple::sm_Tunables.GetTuning("Death_SmokeGrenade");
|
||
|
if (pTuning != NULL)
|
||
|
{
|
||
|
pTaskPhysicalResponse = rage_new CTaskNMControl(500, 5000, rage_new CTaskNMSimple(*pTuning), CTaskNMControl::ALL_FLAGS_CLEAR);
|
||
|
bUseRagdollReaction = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(!pTaskPhysicalResponse)
|
||
|
{ // Make a injured/die task.
|
||
|
CDeathSourceInfo info(pFiringEntity, uWeaponHash, fHitDir, iHitBoneTag);
|
||
|
s32 iFlags = flags.IsFlagSet(CPedDamageCalculator::DF_KillPriorToClearedWantedLevel) ? CTaskDyingDead::Flag_DontSendPedKilledShockingEvent : 0;
|
||
|
pTaskPhysicalResponse = rage_new CTaskDyingDead(&info, iFlags);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bool bNeedsTask = false;
|
||
|
bool bNeedsFallTask = false;
|
||
|
tempDamageEvent.ComputeDamageAnim(pHitPed, pFiringEntity, fHitDir, iHitBoneTag, clipSetId, clipId, bNeedsTask, bNeedsFallTask);
|
||
|
|
||
|
if(clipId != CLIP_ID_INVALID)
|
||
|
{
|
||
|
#if __DEV
|
||
|
if(fwAnimManager::GetClipIfExistsBySetId(clipSetId, clipId) == NULL)
|
||
|
{
|
||
|
atHashString clipDictName;
|
||
|
atHashString clipName;
|
||
|
// PH - Trying to catch a crash where an animation is selected that isn't present in the group chosen
|
||
|
if(fwClipSetManager::GetClipDictionaryNameAndClipName(clipSetId, clipId, clipDictName, clipName))
|
||
|
{
|
||
|
weaponAssertf(0, "[Anim] Missing anim (%s, %s)", clipDictName.GetCStr(), clipName.GetCStr());
|
||
|
}
|
||
|
}
|
||
|
#endif // __DEV
|
||
|
if(bNeedsFallTask)
|
||
|
{
|
||
|
if (pWeaponInfo->GetDamageType() == DAMAGE_TYPE_EXPLOSIVE)
|
||
|
{
|
||
|
pTaskPhysicalResponse = rage_new CTaskAnimatedHitByExplosion(clipSetId, clipId);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pTaskPhysicalResponse = rage_new CTaskFallAndGetUp(clipSetId, clipId, 0.001f);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(!pHitPed->GetPedResetFlag(CPED_RESET_FLAG_BlockAnimatedWeaponReactions))
|
||
|
{
|
||
|
CGenericClipHelper& helper = pHitPed->GetMovePed().GetAdditiveHelper();
|
||
|
helper.BlendInClipBySetAndClip(pHitPed, clipSetId, clipId);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (pResult)
|
||
|
{
|
||
|
const bool bGasWeapon = uWeaponHash == WEAPONTYPE_SMOKEGRENADE || uWeaponHash == WEAPONTYPE_DLC_BOMB_GAS || uWeaponHash == WEAPONTYPE_DLC_BZGAS_MK2;
|
||
|
if(!pHitPed->GetPedResetFlag(CPED_RESET_FLAG_BlockIkWeaponReactions) && !bGasWeapon)
|
||
|
{
|
||
|
// Only sending IK react request when one or both entities are local since non-local reactions are already handled in DoWeaponImpact (avoids duplicate requests)
|
||
|
const bool bHitPedLocal = !NetworkUtils::GetNetworkObjectFromEntity(pHitPed) || !NetworkUtils::IsNetworkClone(pHitPed);
|
||
|
const bool bFiringEntityLocal = !NetworkUtils::GetNetworkObjectFromEntity(pFiringEntity) || !NetworkUtils::IsNetworkClone(pFiringEntity);
|
||
|
|
||
|
if (bHitPedLocal || bFiringEntityLocal)
|
||
|
{
|
||
|
Vec3V vPosition(VECTOR3_TO_VEC3V(pResult->GetHitPosition()));
|
||
|
Vec3V vDirection(Subtract(vPosition, VECTOR3_TO_VEC3V(vStart)));
|
||
|
vDirection = NormalizeFast(vDirection);
|
||
|
|
||
|
CIkRequestBodyReact bodyReactRequest(vPosition, vDirection, (int)pResult->GetHitComponent());
|
||
|
bodyReactRequest.SetWeaponGroup(pWeaponInfo->GetGroup());
|
||
|
pHitPed->GetIkManager().Request(bodyReactRequest);
|
||
|
}
|
||
|
|
||
|
// If player ped, play facial pain anim. Non-player peds get pain animations kicked off from TaskNMShot.
|
||
|
if (pHitPed->IsPlayer() && pHitPed->GetFacialData())
|
||
|
{
|
||
|
pHitPed->GetFacialData()->PlayPainFacialAnim(pHitPed);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if __BANK
|
||
|
// Record that the animation fallback was used
|
||
|
if (fragInstNMGta::ms_bLogUsedRagdolls && pHitPed && !pHitPed->IsLocalPlayer())
|
||
|
{
|
||
|
if (CTheScripts::GetPlayerIsOnAMission())
|
||
|
{
|
||
|
fragInstNMGta::ms_NumFallbackAnimsUsedCurrentLevel++;
|
||
|
}
|
||
|
fragInstNMGta::ms_NumFallbackAnimsUsedGlobally++;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
// Apply any player damage over time effects
|
||
|
if (pWeaponInfo->GetPlayerDamageOverTimeWeapon() != 0 && pHitPed->IsLocalPlayer() && !flags.IsFlagSet(CPedDamageCalculator::DF_MeleeDamage))
|
||
|
{
|
||
|
pHitPed->GetPlayerInfo()->GetDamageOverTime().Start(pWeaponInfo->GetPlayerDamageOverTimeWeapon(), pHitPed, pFiringEntity);
|
||
|
}
|
||
|
|
||
|
// And the event and physical response (if we should and if one was found).
|
||
|
bool bPhysicalResponseAdded = false;
|
||
|
|
||
|
if (pHitPed->IsNetworkClone())
|
||
|
{
|
||
|
CTask *pCloneTask = pHitPed->GetPedIntelligence()->GetTaskActive();
|
||
|
|
||
|
if(pCloneTask && (pCloneTask->GetTaskType() == CTaskTypes::TASK_SYNCHRONIZED_SCENE))
|
||
|
{
|
||
|
bAllowTempDamageEventToBeAdded = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(bAllowTempDamageEventToBeAdded)
|
||
|
{
|
||
|
// Don't add a new event if we just updated a previous one
|
||
|
if (bUpdatedPreviousEvent)
|
||
|
return (tempDamageEvent.GetDamageResponseData().m_fHealthLost + tempDamageEvent.GetDamageResponseData().m_fArmourLost);
|
||
|
|
||
|
if (pTaskPhysicalResponse)
|
||
|
{
|
||
|
tempDamageEvent.SetPhysicalResponseTask(pTaskPhysicalResponse, bUseRagdollReaction);
|
||
|
bPhysicalResponseAdded = true;
|
||
|
}
|
||
|
|
||
|
if (pHitPed->IsNetworkClone() && !pHitPed->GetIsInVehicle())
|
||
|
{
|
||
|
if (pTaskPhysicalResponse && pTaskPhysicalResponse->IsClonedFSMTask() && (pTaskPhysicalResponse->GetTaskType() != CTaskTypes::TASK_DAMAGE_ELECTRIC))
|
||
|
{
|
||
|
CTaskFSMClone* pTaskCopy = static_cast<CTaskFSMClone*>(pTaskPhysicalResponse->Copy());
|
||
|
bool bTaskUsed = false;
|
||
|
|
||
|
#if CNC_MODE_ENABLED
|
||
|
if (pHitPed->GetPedIntelligence()->GetTaskActive()->GetTaskType() == CTaskTypes::TASK_INCAPACITATED)
|
||
|
{
|
||
|
if (tempDamageEvent.GetEventPriority() > E_PRIORITY_INCAPACITATED && pTaskCopy->GetTaskType() == CTaskTypes::TASK_NM_CONTROL)
|
||
|
{
|
||
|
CTaskIncapacitated* pIncapTask = (CTaskIncapacitated*)pHitPed->GetPedIntelligence()->GetTaskActive();
|
||
|
pTaskCopy->SetRunningLocally(true);
|
||
|
pTaskCopy->SetRunAsAClone(true);
|
||
|
pTaskCopy->SetPriority(PED_TASK_PRIORITY_EVENT_RESPONSE_TEMP);
|
||
|
|
||
|
pIncapTask->SetForcedNaturalMotionTask(pTaskCopy);
|
||
|
bTaskUsed = true;
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
if(flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ))
|
||
|
{
|
||
|
pHitPed->GetPedIntelligence()->AddLocalCloneTask(pTaskCopy, PED_TASK_PRIORITY_EVENT_RESPONSE_TEMP);
|
||
|
bTaskUsed = true;
|
||
|
}
|
||
|
else if(pWeaponInfo->GetDamageType()==DAMAGE_TYPE_BULLET)
|
||
|
{
|
||
|
if (pHitPed->IsAPlayerPed())
|
||
|
{
|
||
|
if(pHitPed->GetPedIntelligence()->NetworkLocalHitReactionsAllowed())
|
||
|
{
|
||
|
if (!pTaskCopy->HandlesDeadPed() && pTaskCopy->GetTaskType() == CTaskTypes::TASK_NM_CONTROL)
|
||
|
{
|
||
|
CDeathSourceInfo info(pFiringEntity, uWeaponHash);
|
||
|
CTaskDyingDead* pDeathTask = rage_new CTaskDyingDead(&info);
|
||
|
pDeathTask->SetForcedNaturalMotionTask(pTaskCopy);
|
||
|
pTaskCopy = pDeathTask;
|
||
|
}
|
||
|
|
||
|
if (fWeaponDamage > 0.0f && CGameWorld::FindLocalPlayer() == pFiringEntity && static_cast<CNetObjPlayer*>(pHitPed->GetNetworkObject())->SetDeathResponseTask(pTaskCopy))
|
||
|
{
|
||
|
bTaskUsed = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (pResult && !pHitPed->GetPedResetFlag(CPED_RESET_FLAG_BlockAnimatedWeaponReactions))
|
||
|
{
|
||
|
Vec3V vPosition(VECTOR3_TO_VEC3V(pResult->GetHitPosition()));
|
||
|
Vec3V vDirection(Subtract(vPosition, VECTOR3_TO_VEC3V(vStart)));
|
||
|
vDirection = NormalizeFast(vDirection);
|
||
|
|
||
|
CIkRequestBodyReact bodyReactRequest(vPosition, vDirection, (int)pResult->GetHitComponent());
|
||
|
bodyReactRequest.SetWeaponGroup(pWeaponInfo->GetGroup());
|
||
|
pHitPed->GetIkManager().Request(bodyReactRequest);
|
||
|
|
||
|
// If player ped, play facial pain anim. Non-player peds get pain animations kicked off from TaskNMShot.
|
||
|
if (pHitPed->GetFacialData())
|
||
|
{
|
||
|
pHitPed->GetFacialData()->PlayPainFacialAnim(pHitPed);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(pHitPed->GetPedIntelligence()->NetworkLocalHitReactionsAllowed())
|
||
|
{
|
||
|
pHitPed->GetPedIntelligence()->AddLocalCloneTask(pTaskCopy, PED_TASK_PRIORITY_EVENT_RESPONSE_TEMP);
|
||
|
bTaskUsed = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if(uWeaponHash == WEAPONTYPE_DLC_SNOWBALL && pTaskCopy->GetTaskType() == CTaskTypes::TASK_NM_CONTROL)
|
||
|
{
|
||
|
if(pHitPed->GetPedIntelligence()->NetworkLocalHitReactionsAllowed())
|
||
|
{
|
||
|
pHitPed->GetPedIntelligence()->AddLocalCloneTask(pTaskCopy, PED_TASK_PRIORITY_EVENT_RESPONSE_TEMP);
|
||
|
bTaskUsed = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!bTaskUsed)
|
||
|
{
|
||
|
delete pTaskCopy;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (tempDamageEvent.RequiresAbortForRagdoll() && !pHitPed->GetUsingRagdoll() && !bDeferRagdollActivation)
|
||
|
{
|
||
|
pHitPed->SwitchToRagdoll(tempDamageEvent);
|
||
|
}
|
||
|
else if (!pHitPed->IsNetworkClone())
|
||
|
{
|
||
|
pHitPed->GetPedIntelligence()->AddEvent(tempDamageEvent);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!bPhysicalResponseAdded && pTaskPhysicalResponse)
|
||
|
{
|
||
|
delete pTaskPhysicalResponse;
|
||
|
}
|
||
|
|
||
|
// Let the caller know how much damage was done.
|
||
|
return (tempDamageEvent.GetDamageResponseData().m_fHealthLost + tempDamageEvent.GetDamageResponseData().m_fArmourLost);
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
float CWeaponDamage::ApplyFallOffDamageModifier(const CEntity* pHitEntity, const CWeaponInfo* pWeaponInfo, const Vector3& vWeaponPos, const float fFallOffRangeModifier, const float fFallOffDamageModifier, const float fOrigDamage, CEntity* pFiringEntity)
|
||
|
{
|
||
|
weaponAssert(pWeaponInfo);
|
||
|
|
||
|
float fDamage = fOrigDamage;
|
||
|
|
||
|
// Do not apply for projectiles that are being used in melee.
|
||
|
bool bDoingMeleeWithProjectile = false;
|
||
|
if (pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pFiringPed = static_cast<CPed*>(pFiringEntity);
|
||
|
if (pFiringPed && pFiringPed->GetPedIntelligence() && (pFiringPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_MELEE) || pFiringPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsPerformingVehicleMelee))
|
||
|
&& pWeaponInfo && pWeaponInfo->GetIsProjectile())
|
||
|
{
|
||
|
bDoingMeleeWithProjectile = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(pHitEntity && !pWeaponInfo->GetIsMelee() && !bDoingMeleeWithProjectile)
|
||
|
{
|
||
|
Vector3 vDistance = VEC3V_TO_VECTOR3(pHitEntity->GetTransform().GetPosition()) - vWeaponPos;
|
||
|
float fDistance = vDistance.Mag();
|
||
|
|
||
|
const float fFallOffRangeMin = pWeaponInfo->GetDamageFallOffRangeMin() * fFallOffRangeModifier;
|
||
|
const float fFallOffRangeMax = pWeaponInfo->GetDamageFallOffRangeMax() * fFallOffRangeModifier;
|
||
|
const float fFallOffDamageMod = pWeaponInfo->GetDamageFallOffModifier() * fFallOffDamageModifier;
|
||
|
if(fDistance > fFallOffRangeMax)
|
||
|
{
|
||
|
fDamage *= fFallOffDamageMod;
|
||
|
}
|
||
|
else if(fDistance > fFallOffRangeMin)
|
||
|
{
|
||
|
float fFallOff = (fDistance - fFallOffRangeMin) / (fFallOffRangeMax - fFallOffRangeMin);
|
||
|
fDamage = Lerp(fFallOff, fDamage, fDamage * fFallOffDamageMod);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fDamage;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
f32 CWeaponDamage::DoWeaponImpactPed(CWeapon* pWeapon, CEntity* pFiringEntity, const Vector3& vWeaponPos, WorldProbe::CShapeTestHitPoint* pResult, const f32 fApplyDamage, const bool bTemporaryNetworkWeapon, const fwFlags32& flags, const sMeleeInfo *pMeleeInfo, const float fRecoilAccuracyWhenFired, bool* bGenerateVFX, u8 damageAggregationCount)
|
||
|
{
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpactPed");
|
||
|
|
||
|
// Get the ped we've hit
|
||
|
CEntity* pHitEntity = CPhysics::GetEntityFromInst(pResult->GetHitInst());
|
||
|
weaponFatalAssertf(pHitEntity && pHitEntity->GetIsTypePed(), "pHitEntity is NULL or not a Ped");
|
||
|
CPed* pHitPed = static_cast<CPed*>(pHitEntity);
|
||
|
bool bHitPedWasDead = (pHitPed && pHitPed->ShouldBeDead());
|
||
|
|
||
|
if (flags.IsFlagSet( CPedDamageCalculator::DF_VehicleMeleeHit ))
|
||
|
{
|
||
|
pHitPed->SetPedResetFlag(CPED_RESET_FLAG_WasHitByVehicleMelee, true);
|
||
|
}
|
||
|
|
||
|
f32 fAppliedDamage = fApplyDamage;
|
||
|
if(pFiringEntity && NetworkUtils::IsNetworkClone(pFiringEntity))
|
||
|
{
|
||
|
u32 nIndexFound = 0;
|
||
|
|
||
|
if(pHitPed->IsNetworkClone())
|
||
|
{
|
||
|
//! Network damage events need to generate the audio event as this is done in locally run (non clone ped) code.
|
||
|
if(bTemporaryNetworkWeapon && pMeleeInfo && pResult && flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pFiringPed = static_cast<CPed*>( pFiringEntity );
|
||
|
const CActionResult *pActionResult = ACTIONMGR.FindActionResult( nIndexFound, pMeleeInfo->uParentMeleeActionResultID );
|
||
|
if (pActionResult)
|
||
|
{
|
||
|
pFiringPed->GetPedAudioEntity()->PlayMeleeCombatHit( pActionResult->GetSoundID(), *pResult, pActionResult BANK_ONLY(, pActionResult->GetName() ) );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Clone has hit Clone ped so don't allow setting damage or progressing to set ragdoll on the hit ped on this machine
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
// Decide which damage event gets to do melee damage.
|
||
|
if(flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pFiringPed = static_cast<CPed*>( pFiringEntity );
|
||
|
|
||
|
static dev_bool s_bCacheRemoteMeleeDamage = true;
|
||
|
|
||
|
bool bDoMeleeDamage = false;
|
||
|
|
||
|
if (flags.IsFlagSet( CPedDamageCalculator::DF_VehicleMeleeHit) && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pFiringPed = static_cast<CPed*>( pFiringEntity );
|
||
|
|
||
|
CTaskMountThrowProjectile* pProjTask = static_cast<CTaskMountThrowProjectile*>(pFiringPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_MOUNT_THROW_PROJECTILE));
|
||
|
|
||
|
if (pProjTask && pFiringPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsPerformingVehicleMelee))
|
||
|
{
|
||
|
if (!pProjTask->EntityAlreadyHitByResult(pHitEntity))
|
||
|
{
|
||
|
if (pProjTask->GetIsCloneInDamageWindow())
|
||
|
{
|
||
|
bDoMeleeDamage = true;
|
||
|
pProjTask->AddToHitEntityCount(pHitPed);
|
||
|
pFiringPed->GetPedAudioEntity()->PlayBikeMeleeCombatHit(*pResult);
|
||
|
}
|
||
|
// Cache off damage to wait for correct time
|
||
|
else
|
||
|
{
|
||
|
CTaskMountThrowProjectile::CacheMeleeNetworkDamage(pHitPed,
|
||
|
pFiringPed,
|
||
|
pResult->GetHitPosition(),
|
||
|
pResult->GetHitComponent(),
|
||
|
fAppliedDamage,
|
||
|
flags,
|
||
|
pMeleeInfo->uParentMeleeActionResultID,
|
||
|
pMeleeInfo->uForcedReactionDefinitionID,
|
||
|
pMeleeInfo->uNetworkActionID,
|
||
|
pResult->GetHitMaterialId());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Just allow clone to damage
|
||
|
bDoMeleeDamage = true;
|
||
|
}
|
||
|
}
|
||
|
else if(s_bCacheRemoteMeleeDamage)
|
||
|
{
|
||
|
bool bRemoteHitFromClonePed = bTemporaryNetworkWeapon;
|
||
|
|
||
|
if(flags.IsFlagSet(CPedDamageCalculator::DF_AllowCloneMeleeDamage))
|
||
|
{
|
||
|
bDoMeleeDamage = true;
|
||
|
}
|
||
|
else if(bRemoteHitFromClonePed)
|
||
|
{
|
||
|
u16 meleeID = pMeleeInfo ? pMeleeInfo->uNetworkActionID : 0;
|
||
|
|
||
|
//! Get melee task this ped.
|
||
|
CTaskMeleeActionResult* pFiringPedMeleeActionResultTask = pFiringPed->GetPedIntelligence()->GetTaskMeleeActionResult();
|
||
|
if(pFiringPedMeleeActionResultTask &&
|
||
|
(pFiringPedMeleeActionResultTask->GetUniqueNetworkActionID() == meleeID) &&
|
||
|
(pFiringPedMeleeActionResultTask->GetState() <= CTaskMeleeActionResult::State_PlayClip) &&
|
||
|
pResult &&
|
||
|
pFiringPedMeleeActionResultTask->IsOffensiveMove())
|
||
|
{
|
||
|
if(!pFiringPedMeleeActionResultTask->EntityAlreadyHitByResult(pHitPed))
|
||
|
{
|
||
|
if(pFiringPedMeleeActionResultTask->ShouldProcessMeleeCollisions())
|
||
|
{
|
||
|
//! In damage window - apply damage.
|
||
|
bDoMeleeDamage = true;
|
||
|
pFiringPedMeleeActionResultTask->AddToHitEntityCount(pHitPed, pResult->GetHitComponent());
|
||
|
}
|
||
|
else if(pFiringPedMeleeActionResultTask->HasStartedProcessingCollisions() && !pFiringPedMeleeActionResultTask->ShouldProcessMeleeCollisions())
|
||
|
{
|
||
|
//! Passed damage window. Apply damage.
|
||
|
//! Note: may want some special logic in here.
|
||
|
bDoMeleeDamage = true;
|
||
|
pFiringPedMeleeActionResultTask->AddToHitEntityCount(pHitPed, pResult->GetHitComponent());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//! Pass damage event on to melee task to process when it can.
|
||
|
CTaskMeleeActionResult::CacheMeleeNetworkDamage(pHitPed,
|
||
|
pFiringPed,
|
||
|
pResult->GetHitPosition(),
|
||
|
pResult->GetHitComponent(),
|
||
|
fAppliedDamage,
|
||
|
flags,
|
||
|
pMeleeInfo->uParentMeleeActionResultID,
|
||
|
pMeleeInfo->uForcedReactionDefinitionID,
|
||
|
pMeleeInfo->uNetworkActionID);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
//! Ped isn't doing melee attack anymore, so won't be synced up. Is this ok?
|
||
|
bDoMeleeDamage = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bDoMeleeDamage = true;
|
||
|
}
|
||
|
|
||
|
//! Network damage events need to generate the audio event as this is done in locally run (non clone ped) code.
|
||
|
const CActionResult *pActionResult = ACTIONMGR.FindActionResult( nIndexFound, pMeleeInfo->uParentMeleeActionResultID );
|
||
|
if(bTemporaryNetworkWeapon && pMeleeInfo && pResult && pActionResult)
|
||
|
{
|
||
|
pFiringPed->GetPedAudioEntity()->PlayMeleeCombatHit( pActionResult->GetSoundID(), *pResult, pActionResult BANK_ONLY(, pActionResult->GetName() ) );
|
||
|
}
|
||
|
|
||
|
if(!bDoMeleeDamage)
|
||
|
{
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// A shot generated by a clone ped is not allowed to damage or effect the ped (except dead peds)
|
||
|
// If m_temporaryNetworkWeapon is set then this impact has been generated by a damage
|
||
|
// event from another machine and is therefore allowed to do the proper damage locally
|
||
|
else if(!bTemporaryNetworkWeapon && !(pHitPed && pHitPed->GetIsDeadOrDying()))
|
||
|
{
|
||
|
if (pWeapon && pWeapon->GetWeaponInfo()->GetGroup() == WEAPONGROUP_SHOTGUN)
|
||
|
fAppliedDamage = 0.0f;
|
||
|
else
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SetLastSignificantShotBone(pWeapon, pHitPed, pResult);
|
||
|
|
||
|
Vector3 vStart = vWeaponPos;
|
||
|
f32 fDamageResult = GeneratePedDamageEvent(pFiringEntity, pHitPed, pWeapon->GetWeaponHash(), fAppliedDamage, vStart, pResult, flags, pWeapon, pResult ? pResult->GetHitComponent() : -1, 0.0f, pMeleeInfo, false, fRecoilAccuracyWhenFired, bTemporaryNetworkWeapon, damageAggregationCount);
|
||
|
|
||
|
// Only damage bodypart if it is actually damaged
|
||
|
if (fDamageResult > 0.f && pResult)
|
||
|
{
|
||
|
// I am using this to also decide if the ped should bleed out or not
|
||
|
if (pFiringEntity &&
|
||
|
pFiringEntity->GetIsTypePed() &&
|
||
|
pWeapon->GetWeaponInfo()->GetEffectGroup() >= WEAPON_EFFECT_GROUP_PISTOL_SMALL && // Don't accept melee
|
||
|
pHitPed->GetWeaponDamageEntity())
|
||
|
{
|
||
|
CPed* pFiringPed = (CPed*)pFiringEntity;
|
||
|
if (!pHitPed->GetPedIntelligence()->IsFriendlyWith(*pFiringPed))
|
||
|
{
|
||
|
s32 iHitComponent = pResult->GetHitComponent();
|
||
|
s32 iHitBoneTag = pHitPed->GetBoneTagFromRagdollComponent(iHitComponent);
|
||
|
pHitPed->ReportBodyPartDamage(CPed::ConvertBoneToBodyPart(iHitBoneTag));
|
||
|
|
||
|
if (!pHitPed->HasHurtStarted() && !pHitPed->IsBodyPartDamaged(CPed::DAMAGED_GOTOWRITHE) && pHitPed->CanBeInHurt() && !pHitPed->GetPedConfigFlag( CPED_CONFIG_FLAG_DisableGoToWritheWhenInjured ) && !pHitPed->GetPedConfigFlag(CPED_CONFIG_FLAG_CanBeArrested))
|
||
|
{
|
||
|
if (pHitPed->IsBodyPartDamaged(CPed::DAMAGED_LEFT_LEG | CPed::DAMAGED_RIGHT_LEG))
|
||
|
pHitPed->ReportBodyPartDamage(CPed::DAMAGED_GOTOWRITHE);
|
||
|
|
||
|
if (pWeapon->GetWeaponInfo()->GetEffectGroup() == WEAPON_EFFECT_GROUP_STUNGUN)
|
||
|
pHitPed->ReportBodyPartDamage(CPed::DAMAGED_GOTOWRITHE | CPed::DAMAGED_STUN);
|
||
|
|
||
|
// If you get shot whilst falling over we roll the writhe dice for every hit
|
||
|
static dev_float WRITHE_CHANCE = 0.5f;
|
||
|
if (fwRandom::GetRandomNumberInRange(0.f, 1.f) < WRITHE_CHANCE)
|
||
|
{
|
||
|
CTaskNMControl* pNMTask = smart_cast<CTaskNMControl*>(pHitPed->GetPedIntelligence()->FindTaskActiveByType(CTaskTypes::TASK_NM_CONTROL));
|
||
|
if (pNMTask && pNMTask->IsFeedbackFlagSet(CTaskNMControl::BALANCE_FAILURE))
|
||
|
pHitPed->ReportBodyPartDamage(CPed::DAMAGED_GOTOWRITHE);
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Intelligence
|
||
|
if(pWeapon && (pWeapon->GetDamageType() == DAMAGE_TYPE_BULLET))
|
||
|
{
|
||
|
pHitPed->GetPedIntelligence()->SetLastTimeShot(fwTimer::GetTimeInMilliseconds());
|
||
|
}
|
||
|
|
||
|
// Player dialogue
|
||
|
if(pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pFiringPed = static_cast<CPed*>(pFiringEntity);
|
||
|
if(pFiringPed->IsLocalPlayer() && pFiringPed->GetSpeechAudioEntity())
|
||
|
{
|
||
|
if(!bHitPedWasDead && pHitPed->ShouldBeDead() && pHitPed->GetPedType() != PEDTYPE_ANIMAL)
|
||
|
{
|
||
|
// We are actually killing the last guy and he is still considered in combat at this point so rather than checking for no peds we check for 1
|
||
|
if(pFiringPed->GetPlayerInfo()->GetNumEnemiesInCombat() == 1)
|
||
|
{
|
||
|
pFiringPed->GetSpeechAudioEntity()->SayWhenSafe("KILLED_ALL");
|
||
|
}
|
||
|
/*else
|
||
|
{
|
||
|
pFiringPed->GetSpeechAudioEntity()->SayWhenSafe("STAY_DOWN");
|
||
|
}*/
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(pFiringEntity && pFiringEntity->GetIsTypePed() && pWeapon && pWeapon->GetDamageType() == DAMAGE_TYPE_BULLET)
|
||
|
{
|
||
|
CPed* pFiringPed = static_cast<CPed*>(pFiringEntity);
|
||
|
if(pFiringPed->IsPlayer())
|
||
|
{
|
||
|
// 'Flaming Bullet' Cheat
|
||
|
if (pFiringPed->GetPlayerInfo()->GetPlayerResetFlags().IsFlagSet(CPlayerResetFlags::PRF_FIRE_AMMO_ON) && CFireManager::CanSetPedOnFire(pHitPed))
|
||
|
{
|
||
|
g_fireMan.StartPedFire(pHitPed, pFiringPed, FIRE_DEFAULT_NUM_GENERATIONS, Vec3V(V_ZERO), BANK_ONLY(Vec3V(V_ZERO),) false, false, pWeapon ? pWeapon->GetWeaponHash() : 0);
|
||
|
}
|
||
|
|
||
|
if (pFiringPed->IsLocalPlayer() && pHitPed->GetNetworkObject() && !pHitPed->GetNetworkObject()->IsClone() && !pHitPed->GetNetworkObject()->IsPendingOwnerChange())
|
||
|
{
|
||
|
// if the local player is shooting at a local ped, prevent him from migrating to another machine for a while so that the shot reactions look better
|
||
|
if (!PARAM_invincibleMigratingPeds.Get())
|
||
|
{
|
||
|
static_cast<CNetObjPed*>(pHitPed->GetNetworkObject())->KeepProximityControl();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Projectile Impact (Flare Gun)
|
||
|
if (pWeapon->GetWeaponInfo()->GetIsProjectile() && !flags.IsFlagSet(CPedDamageCalculator::DF_MeleeDamage))
|
||
|
{
|
||
|
const CAmmoProjectileInfo* pAmmoInfo = static_cast<const CAmmoProjectileInfo*>(pWeapon->GetWeaponInfo()->GetAmmoInfo());
|
||
|
if (pAmmoInfo && pAmmoInfo->GetShouldSetOnFireOnImpact() && CFireManager::CanSetPedOnFire(pHitPed))
|
||
|
{
|
||
|
g_fireMan.StartPedFire(pHitPed, pFiringPed, FIRE_DEFAULT_NUM_GENERATIONS, Vec3V(V_ZERO), BANK_ONLY(Vec3V(V_ZERO),) false, false, pWeapon ? pWeapon->GetWeaponHash() : 0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Check if we need to register a hit with the damage overlay effect
|
||
|
eDamageType damageType = DAMAGE_TYPE_UNKNOWN;
|
||
|
if (pWeapon)
|
||
|
{
|
||
|
damageType = pWeapon->GetDamageType();
|
||
|
}
|
||
|
|
||
|
// Only interested if hit ped is the local player (and we're not transitioning)
|
||
|
if (pHitPed->IsLocalPlayer() && !NetworkInterface::IsSessionLeaving() && !g_PlayerSwitch.IsActive())
|
||
|
{
|
||
|
// Check if we need to do endurance effects instead
|
||
|
bool bIsEnduranceDamage = IsEnduranceDamage(pHitPed, pWeapon, flags);
|
||
|
if (damageType == DAMAGE_TYPE_BULLET || damageType == DAMAGE_TYPE_EXPLOSIVE)
|
||
|
{
|
||
|
PostFX::RegisterBulletImpact(vWeaponPos, 1.0f, bIsEnduranceDamage);
|
||
|
}
|
||
|
else if ((damageType == DAMAGE_TYPE_MELEE) && pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
Vector3 vFiringPedPos = VEC3V_TO_VECTOR3(pFiringEntity->GetTransform().GetPosition());
|
||
|
PostFX::RegisterBulletImpact(vFiringPedPos, 1.0f, bIsEnduranceDamage);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bGenerateVFX && pHitPed->IsPlayer() && pHitPed->IsNetworkClone())
|
||
|
{
|
||
|
// Don't display any VFX on invincible respawning players
|
||
|
CNetObjPlayer* netObjPlayer = static_cast<CNetObjPlayer *>(pHitPed->GetNetworkObject());
|
||
|
if(netObjPlayer && netObjPlayer->GetRespawnInvincibilityTimer() > 0)
|
||
|
{
|
||
|
*bGenerateVFX = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fDamageResult;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
dev_float dfMaxMeleeDeform = 0.15f;
|
||
|
dev_float MELEE_WEAPON_VEHICLE_DEFORM_MULT = 0.0025f;
|
||
|
const float FORCE_THRESHOLD_FOR_HIGH_UPDATE = 1000.0f;
|
||
|
const unsigned BLENDER_OVERRIDE_LENGTH = 200;
|
||
|
f32 CWeaponDamage::DoWeaponImpactCar(CWeapon* pWeapon, CEntity* pFiringEntity, const Vector3& vWeaponPos, WorldProbe::CShapeTestHitPoint* pResult, const f32 fApplyDamage, const bool bFireDriveby, const bool bTemporaryNetworkWeapon, const fwFlags32& flags, Vector3* hitDirOut /* = 0 */, const Vector3* hitDirIn /* = 0 */, bool bShouldApplyDamageToGlass /* = true */)
|
||
|
{
|
||
|
|
||
|
CEntity* pHitEntity = CPhysics::GetEntityFromInst(pResult->GetHitInst());
|
||
|
weaponFatalAssertf(pHitEntity && pHitEntity->GetIsTypeVehicle(), "pHitEntity is NULL or not a Vehicle");
|
||
|
CVehicle* pHitVehicle = static_cast<CVehicle*>(pHitEntity);
|
||
|
|
||
|
bool bFirerInSameVehicleAsHitEntity = false;
|
||
|
if (NetworkInterface::IsGameInProgress())
|
||
|
{
|
||
|
if(fApplyDamage > FORCE_THRESHOLD_FOR_HIGH_UPDATE)
|
||
|
{
|
||
|
if(pHitVehicle->IsNetworkClone())
|
||
|
{
|
||
|
NetworkInterface::OverrideNetworkBlenderForTime(static_cast<CPhysical*>(pHitEntity), BLENDER_OVERRIDE_LENGTH);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
NetworkInterface::ForceHighUpdateLevel(static_cast<CPhysical*>(pHitEntity));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pFiringPed = static_cast<CPed*>(pFiringEntity);
|
||
|
if (pFiringPed && pFiringPed->GetIsInVehicle() && (pFiringPed->GetMyVehicle() == pHitVehicle))
|
||
|
bFirerInSameVehicleAsHitEntity = true;
|
||
|
}
|
||
|
|
||
|
if (!bFirerInSameVehicleAsHitEntity)
|
||
|
{
|
||
|
// A local shot hitting a clone is not allowed to damage or move it
|
||
|
// or, a shot generated by a clone task running on a cloned entity.
|
||
|
// If m_temporaryNetworkWeapon is set then this impact has been generated by a damage
|
||
|
// event from another machine and is therefore allowed to do the proper damage locally
|
||
|
//
|
||
|
// Allow melee damage on remotes
|
||
|
if( !flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) )
|
||
|
{
|
||
|
if(NetworkUtils::IsNetworkCloneOrMigrating(pHitVehicle) || (pFiringEntity && NetworkUtils::IsNetworkClone(pFiringEntity) && !bTemporaryNetworkWeapon))
|
||
|
{
|
||
|
weaponDebugf3("(NetworkUtils::IsNetworkCloneOrMigrating(pHitVehicle) || (pFiringEntity && NetworkUtils::IsNetworkClone(pFiringEntity) && !bTemporaryNetworkWeapon))-->return 0.0f");
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Get direction of shot
|
||
|
Vector3 vDir = pResult->GetHitPosition() - vWeaponPos;
|
||
|
vDir.Normalize();
|
||
|
if(hitDirOut)
|
||
|
{
|
||
|
*hitDirOut = vDir;
|
||
|
}
|
||
|
|
||
|
if(hitDirIn)
|
||
|
{
|
||
|
vDir = *hitDirIn;
|
||
|
}
|
||
|
|
||
|
// Compute the force
|
||
|
f32 fShootForce;
|
||
|
|
||
|
if( flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) )
|
||
|
{
|
||
|
weaponDebugf3("DF_MeleeDamage");
|
||
|
|
||
|
// For melee damage, we scale the force by the damage directly
|
||
|
// This allows the melee system to specify weak and strong attacks
|
||
|
// with the same weapon from the melee action table
|
||
|
fShootForce = fApplyDamage * MELEE_WEAPON_FORCE_MULT;
|
||
|
|
||
|
// We want the deformation to be visually the same regardless of mass
|
||
|
f32 fScaleFactor = MELEE_WEAPON_VEHICLE_DEFORM_MULT * pHitVehicle->GetMass();
|
||
|
#if __BANK
|
||
|
if( CVehicleDeformation::ms_fWeaponImpactDamageScale != 0.0f )
|
||
|
{
|
||
|
fScaleFactor *= CVehicleDeformation::ms_fWeaponImpactDamageScale;
|
||
|
}
|
||
|
#endif
|
||
|
f32 fDeformCapMult = 1.0f;
|
||
|
if(pHitVehicle->GetVehicleDamage()->GetDeformation()->HasDamageTexture())
|
||
|
{
|
||
|
void* pTexLock = pHitVehicle->GetVehicleDamage()->GetDeformation()->LockDamageTexture(grcsRead);
|
||
|
if(pTexLock)
|
||
|
{
|
||
|
Vector3 vHitOffset = VEC3V_TO_VECTOR3(pHitVehicle->GetTransform().UnTransform(VECTOR3_TO_VEC3V(pResult->GetHitPosition())));
|
||
|
Vector3 vCurrentDeformation = VEC3V_TO_VECTOR3(pHitVehicle->GetVehicleDamage()->GetDeformation()->ReadFromVectorOffset(pTexLock, VECTOR3_TO_VEC3V(vHitOffset)));
|
||
|
fDeformCapMult = 1.0f - Clamp(vCurrentDeformation.Mag() / dfMaxMeleeDeform, 0.0f, 1.0f);
|
||
|
pHitVehicle->GetVehicleDamage()->GetDeformation()->UnLockDamageTexture();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Do vehicle deformation for melee damage
|
||
|
if(pHitVehicle->GetVehicleDamage()->GetDeformation()->ApplyCollisionImpact(fScaleFactor * fDeformCapMult * fShootForce * vDir, pResult->GetHitPosition(), pFiringEntity, false, bShouldApplyDamageToGlass))
|
||
|
{
|
||
|
// If we deformed the vehicle due to melee, make sure it gets to update on the next frame, otherwise
|
||
|
// the deformation may be delayed due to timeslicing.
|
||
|
// Note: possibly we could move this into ApplyCollisionImpact(), but for now it's safer to do it outside
|
||
|
// since we don't know about problems in any other cases than deformation from melee, and we have to be
|
||
|
// careful to not make things slower in other vehicle damage cases (during which we may already have
|
||
|
// performance issues, e.g. during explosions and high speed car crashes).
|
||
|
pHitVehicle->m_nVehicleFlags.bLodForceUpdateThisTimeslice = true;
|
||
|
}
|
||
|
|
||
|
// Add a shocking event for the damage if the inflictor is a player.
|
||
|
if (pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pFiringPed = static_cast<CPed*>(pFiringEntity);
|
||
|
if (pFiringPed->IsAPlayerPed())
|
||
|
{
|
||
|
CEventShockingPropertyDamage event(*pFiringPed, pHitVehicle);
|
||
|
CShockingEventsManager::Add(event);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(pHitVehicle->InheritsFromHeli() || pHitVehicle->InheritsFromBoat())
|
||
|
{
|
||
|
DoWeaponImpactSearchLight(pHitEntity, pResult->GetHitPosition());
|
||
|
|
||
|
if(pHitVehicle->IsInAir(false))
|
||
|
{
|
||
|
fShootForce = pWeapon->GetWeaponInfo()->GetForceHitFlyingHeli();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fShootForce = pWeapon->GetWeaponInfo()->GetForceHitVehicle();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fShootForce = pWeapon->GetWeaponInfo()->GetForceHitVehicle();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Scale to some base vehicle weight (<1200kg) so we don't shoot light vehicles across the map
|
||
|
static dev_float WEAPON_VEHICLE_MASS_CAP_MIN = 1000.0f;
|
||
|
static dev_float WEAPON_VEHICLE_MASS_CAP_MAX = 2000.0f;
|
||
|
static dev_float WEAPON_VEHICLE_MASS_REDUCTION_MIN = 0.25f;
|
||
|
static dev_float WEAPON_VEHICLE_NO_WHEELS_REDUCTION = 0.15f;
|
||
|
|
||
|
const float fMass = pHitVehicle->GetMass();
|
||
|
float fShootForceMod = (rage::Max(fMass, WEAPON_VEHICLE_MASS_CAP_MIN)-WEAPON_VEHICLE_MASS_CAP_MIN)/(WEAPON_VEHICLE_MASS_CAP_MAX-WEAPON_VEHICLE_MASS_CAP_MIN);
|
||
|
fShootForceMod = WEAPON_VEHICLE_MASS_REDUCTION_MIN + fShootForceMod*(1.0f-WEAPON_VEHICLE_MASS_REDUCTION_MIN);
|
||
|
fShootForce *= rage::Min(1.0f, rage::Min(fShootForceMod, fMass / WEAPON_VEHICLE_MASS_CAP_MIN));
|
||
|
|
||
|
bool bPowerfulWeapon = false;
|
||
|
if (pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPedWeaponManager* pWeaponMgr = static_cast<CPed*>(pFiringEntity)->GetWeaponManager();
|
||
|
if (pWeaponMgr && pWeaponMgr->GetEquippedWeaponHash() == WEAPONTYPE_MINIGUN)
|
||
|
{
|
||
|
bPowerfulWeapon = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Scale down bullet impulses applied to vehicles, relative to how they align with the vehicle's velocity. This fixes a lot of the
|
||
|
// apparent inconsistencies with how bullets are effecting a vehicle's acceleration.
|
||
|
static dev_float sfSpeedRangeMax = 3.0f;
|
||
|
static dev_float sfNormalWeaponSpeedMultMax = 1.0f;
|
||
|
static dev_float sfPowerfulWeaponSpeedMultMax = 1.8f;
|
||
|
const Vector3 vHitVehicleVelocity = pHitVehicle->GetVelocity();
|
||
|
float fSpeedMultMax = bPowerfulWeapon ? sfPowerfulWeaponSpeedMultMax : sfNormalWeaponSpeedMultMax;
|
||
|
float fVelocityInDirOfBullet = DotProduct(vHitVehicleVelocity, vDir);
|
||
|
fShootForce *= Lerp(ClampRange(fVelocityInDirOfBullet, 0.0f, sfSpeedRangeMax), fSpeedMultMax, 0.0f);
|
||
|
|
||
|
// If a vehicle is resting upside down or on its side reduce the force applied
|
||
|
if( !pHitVehicle->GetIsAircraft() && !pHitVehicle->GetIsAquatic() && (pHitVehicle->HasContactWheels() == false) )
|
||
|
{
|
||
|
fShootForce *= WEAPON_VEHICLE_NO_WHEELS_REDUCTION;
|
||
|
}
|
||
|
|
||
|
static dev_float WEAPON_BIKE_FORCE_REDUCTION = 0.1f;
|
||
|
static dev_float WEAPON_STANDING_TRAILER_FORCE_REDUCTION = 0.2f;
|
||
|
if(pHitVehicle->InheritsFromBike())
|
||
|
{
|
||
|
fShootForce *= WEAPON_BIKE_FORCE_REDUCTION;
|
||
|
}
|
||
|
else if(pHitVehicle->InheritsFromTrailer())
|
||
|
{
|
||
|
if(static_cast<CTrailer*>(pHitVehicle)->AreTrailerLegsLoweredFully())
|
||
|
{
|
||
|
// Even with shrinking the angular impulse the friction on the contacts of the trailer legs will cause the
|
||
|
// trailer to tip unrealistically since the trailer isn't that heavy.
|
||
|
fShootForce *= WEAPON_STANDING_TRAILER_FORCE_REDUCTION;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(pHitVehicle->GetVehicleModelInfo())
|
||
|
{
|
||
|
fShootForce *= pHitVehicle->GetVehicleModelInfo()->GetWeaponForceMult();
|
||
|
}
|
||
|
|
||
|
bool bApplyForce = true;
|
||
|
// Dont apply force when hitting the windows
|
||
|
if( PGTAMATERIALMGR->GetMtlFlagShootThrough(pResult->GetHitMaterialId()) )
|
||
|
{
|
||
|
bApplyForce = false;
|
||
|
}
|
||
|
|
||
|
//don't apply a force for moving superdummy vehicles, it causes them to activate and
|
||
|
//go a little crazy
|
||
|
if (pHitVehicle->IsSuperDummy() && vHitVehicleVelocity.Mag2() > 1.0f)
|
||
|
{
|
||
|
bApplyForce = false;
|
||
|
}
|
||
|
|
||
|
float fDamage = fApplyDamage;
|
||
|
if(pWeapon->GetWeaponInfo()->GetGroup() == WEAPONGROUP_SHOTGUN)
|
||
|
{
|
||
|
TUNE_GROUP_FLOAT(WEAPON_TUNE, SHOTGUN_VEHICLE_DAMAGE_MODIFIER, 0.5f, 0.1f, 2.f, 0.01f);
|
||
|
fDamage *= SHOTGUN_VEHICLE_DAMAGE_MODIFIER;
|
||
|
}
|
||
|
|
||
|
eDamageType eFixedDamageType = pWeapon->GetDamageType();
|
||
|
|
||
|
// Stop melee attacks from grenade launchers passing through DAMAGE_TYPE_EXPLOSIVE
|
||
|
if(eFixedDamageType == DAMAGE_TYPE_EXPLOSIVE && flags.IsFlagSet(CPedDamageCalculator::DF_MeleeDamage))
|
||
|
{
|
||
|
// Weapons apply DAMAGE_TYPE_BULLET for melee attacks
|
||
|
// DAMAGE_TYPE_MELEE only applies a sliver of damage, making it nearly impossible to dent a vehicle
|
||
|
eFixedDamageType = DAMAGE_TYPE_BULLET;
|
||
|
}
|
||
|
|
||
|
bool bMeleeDamage = flags.IsFlagSet(CPedDamageCalculator::DF_MeleeDamage) || flags.IsFlagSet(CPedDamageCalculator::DF_VehicleMeleeHit);
|
||
|
// Apply damage to the health of the various components of the vehicle
|
||
|
f32 fDamageResult = pHitVehicle->GetVehicleDamage()->ApplyDamage(pFiringEntity, eFixedDamageType, pWeapon->GetWeaponHash(), fDamage, pResult->GetHitPosition(), pResult->GetHitNormal(), vDir, pResult->GetHitComponent(), pResult->GetHitMaterialId(), pResult->GetHitPartIndex(), bFireDriveby, flags.IsFlagSet( CPedDamageCalculator::DF_IsAccurate ), 0.0f, false, false, bMeleeDamage );
|
||
|
|
||
|
bool bPlayerResponsible = false;
|
||
|
bool bTurretResponsible = false;
|
||
|
bool bNoForceDueToHitCover = false;
|
||
|
if(pFiringEntity)
|
||
|
{
|
||
|
if(pFiringEntity->GetIsTypePed() && static_cast<CPed*>(pFiringEntity)->IsPlayer())
|
||
|
{
|
||
|
bPlayerResponsible = true;
|
||
|
|
||
|
//Don't apply force to my cover object
|
||
|
CPed* pFiringPlayer = static_cast<CPed*>(pFiringEntity);
|
||
|
if (pFiringPlayer->GetIsInCover())
|
||
|
{
|
||
|
CCoverPoint* pCoverPoint = pFiringPlayer->GetCoverPoint();
|
||
|
if (pCoverPoint && pCoverPoint->GetEntity() == pHitVehicle && bApplyForce)
|
||
|
{
|
||
|
bNoForceDueToHitCover = true;
|
||
|
bApplyForce = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if(pFiringEntity->GetIsDynamic() && static_cast<CDynamicEntity*>(pFiringEntity)->GetStatus() == STATUS_PLAYER)
|
||
|
{
|
||
|
bPlayerResponsible = true;
|
||
|
}
|
||
|
|
||
|
// Also apply force if a turret vehicle weapon is the firing entity
|
||
|
if (pFiringEntity->GetIsTypeVehicle())
|
||
|
{
|
||
|
const CWeaponInfo* pWeaponInfo = pWeapon->GetWeaponInfo();
|
||
|
if (pWeaponInfo && pWeaponInfo->GetIsTurret())
|
||
|
{
|
||
|
bTurretResponsible = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Apply force to vehicle in original direction of damage (don't bother for non-player peds)
|
||
|
if(bPlayerResponsible || bTurretResponsible)
|
||
|
{
|
||
|
Vec3V instancePosition = pHitVehicle->GetTransform().GetPosition();
|
||
|
Vec3V instanceToCenterOfGravity;
|
||
|
if(const fragInst* vehicleFragInst = pHitVehicle->GetFragInst())
|
||
|
{
|
||
|
instanceToCenterOfGravity = Transform3x3(pHitVehicle->GetTransform().GetMatrix(),vehicleFragInst->GetArchetype()->GetBound()->GetCGOffset());
|
||
|
|
||
|
// If this vehicle is articulated, use the CG of the link we hit
|
||
|
if(const fragCacheEntry* cacheEntry = vehicleFragInst->GetCacheEntry())
|
||
|
{
|
||
|
const fragHierarchyInst* hierInst = cacheEntry->GetHierInst();
|
||
|
if(hierInst->articulatedCollider && hierInst->body)
|
||
|
{
|
||
|
int linkIndex = hierInst->articulatedCollider->GetLinkFromComponent(pResult->GetHitComponent());
|
||
|
instanceToCenterOfGravity = Add(instanceToCenterOfGravity,hierInst->body->GetLink(linkIndex).GetPositionV());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
instanceToCenterOfGravity = Vec3V(V_ZERO);
|
||
|
}
|
||
|
Vec3V centerOfGravity = Add(instancePosition,instanceToCenterOfGravity);
|
||
|
|
||
|
// Scale the lever arm of the impulse, as large torques appear to have an overpowering effect on vehicles
|
||
|
static dev_float sfSpinScale = 0.1f;
|
||
|
static dev_float sfRollScale = 0.2f;
|
||
|
Vec3V offsetFromCG = Subtract(pResult->GetHitPositionV(),centerOfGravity);
|
||
|
Vec3V offsetFromInstance = AddScaled(instanceToCenterOfGravity,offsetFromCG,Vec3V(sfSpinScale,sfSpinScale,sfRollScale));
|
||
|
|
||
|
if(pHitVehicle->InheritsFromBike())
|
||
|
{
|
||
|
// If an on-foot melee attack from a player hits a bike in MP, knock off all peds and apply part of the weapon damage to each ped
|
||
|
if(NetworkInterface::IsGameInProgress() && flags.IsFlagSet(CPedDamageCalculator::DF_MeleeDamage) && !flags.IsFlagSet(CPedDamageCalculator::DF_VehicleMeleeHit)
|
||
|
&& pFiringEntity->GetIsTypePed() && static_cast<CPed*>(pFiringEntity)->IsPlayer())
|
||
|
{
|
||
|
const CSeatManager* pSeatManager = pHitVehicle->GetSeatManager();
|
||
|
if (pSeatManager)
|
||
|
{
|
||
|
for(s32 iSeat = 0; iSeat < pSeatManager->GetMaxSeats(); iSeat++)
|
||
|
{
|
||
|
CPed* pPassenger = pSeatManager->GetPedInSeat(iSeat);
|
||
|
if(pPassenger)
|
||
|
{
|
||
|
pPassenger->KnockPedOffVehicle(false, fApplyDamage * 0.75f);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Knock over bike
|
||
|
CBike *pBike = static_cast<CBike*>(pHitVehicle);
|
||
|
pBike->m_nBikeFlags.bOnSideStand = false;
|
||
|
pBike->RemoveConstraint();
|
||
|
}
|
||
|
|
||
|
bool bApplyForceToChassis = false;
|
||
|
|
||
|
// Exclude vehicle doors that are being used
|
||
|
const fragInst* hitFragInst = pHitVehicle->GetFragInst();
|
||
|
if (hitFragInst)
|
||
|
{
|
||
|
if(hitFragInst->GetCacheEntry())
|
||
|
{
|
||
|
if(phArticulatedCollider* pArticulatedCollider = hitFragInst->GetCacheEntry()->GetHierInst()->articulatedCollider)
|
||
|
{
|
||
|
if(pArticulatedCollider->IsArticulated())
|
||
|
{
|
||
|
s32 iComponentIndex = pResult->GetHitComponent();
|
||
|
int hitLink = pArticulatedCollider->GetLinkFromComponent(iComponentIndex);
|
||
|
// If we hit the root link then we can't hit an articulated door
|
||
|
if(hitLink != 0)
|
||
|
{
|
||
|
// Check if the hit component is on the same articulated link as the the door
|
||
|
for (s32 doorIndex=0; doorIndex<pHitVehicle->GetNumDoors(); doorIndex++)
|
||
|
{
|
||
|
const CCarDoor* pDoor = pHitVehicle->GetDoor(doorIndex);
|
||
|
s32 doorComponentIndex = pDoor->GetFragChild();
|
||
|
if (doorComponentIndex > -1 && pArticulatedCollider->GetLinkFromComponent(doorComponentIndex) == hitLink)
|
||
|
{
|
||
|
if (pDoor->GetFlag(CCarDoor::PED_DRIVING_DOOR))
|
||
|
{
|
||
|
// A ped is using this door don't apply any force
|
||
|
bApplyForce = false;
|
||
|
}
|
||
|
else if (pDoor->GetFlag(CCarDoor::PED_USING_DOOR_FOR_COVER))
|
||
|
{
|
||
|
// A ped is using this door as cover, if we apply the force to the door we may invalidate the
|
||
|
// cover as it closes, instead apply force to chassis to get some rockin
|
||
|
bApplyForceToChassis = true;
|
||
|
}
|
||
|
else if(bNoForceDueToHitCover)
|
||
|
{
|
||
|
// Player is hitting the articulated door of his cover vehicle, we still want to apply the force to that door
|
||
|
bApplyForce = true;
|
||
|
}
|
||
|
|
||
|
fShootForce*=GUN_WEAPON_CAR_DOOR_MOD;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check if the hit component is a turret
|
||
|
if(pHitVehicle->GetVehicleWeaponMgr())
|
||
|
{
|
||
|
for(int i = 0; i < pHitVehicle->GetVehicleWeaponMgr()->GetNumTurrets(); i++)
|
||
|
{
|
||
|
CTurret *pTurret = pHitVehicle->GetVehicleWeaponMgr()->GetTurret(i);
|
||
|
if(pTurret->GetType() == VGT_TURRET_PHYSICAL)
|
||
|
{
|
||
|
CTurretPhysical *pTurretPhysical = static_cast<CTurretPhysical*>(pTurret);
|
||
|
s32 turretComponentIndex = pTurretPhysical->GetBaseFragChild();
|
||
|
if(turretComponentIndex > -1 && pArticulatedCollider->GetLinkFromComponent(turretComponentIndex) == hitLink)
|
||
|
{
|
||
|
fShootForce*=GUN_WEAPON_CAR_TURRET_MOD;
|
||
|
break;
|
||
|
}
|
||
|
s32 barrelComponentIndex = pTurretPhysical->GetBarrelFragChild();
|
||
|
if(barrelComponentIndex > -1 && pArticulatedCollider->GetLinkFromComponent(barrelComponentIndex) == hitLink)
|
||
|
{
|
||
|
fShootForce*=GUN_WEAPON_CAR_TURRET_MOD;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// B*1950644: Prevent players that are in a vehicle and somehow shooting it (e.g. Players hanging on the sides of the Granger) from applying large forces that affect the handling.
|
||
|
if(bFirerInSameVehicleAsHitEntity)
|
||
|
{
|
||
|
// Only do this if the vehicle is being actively driven by the throttle.
|
||
|
if(abs(pHitVehicle->GetThrottle()) > 0.25f)
|
||
|
{
|
||
|
// Reduce the force so that it won't make the vehicle slow down or swerve off course, but still makes the vehicle rock a little for effect.
|
||
|
TUNE_GROUP_FLOAT(VEHICLE_PASSENGER_WEAPON_DAMAGE, FORCE_MULTIPLER, 0.2f, 0.0f, 1.0f, 0.01f);
|
||
|
fShootForce *= FORCE_MULTIPLER;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bApplyForceToChassis)
|
||
|
{
|
||
|
pHitVehicle->ApplyExternalImpulse(fShootForce * vDir, RCC_VECTOR3(instanceToCenterOfGravity), 0);
|
||
|
}
|
||
|
else if (bApplyForce)
|
||
|
{
|
||
|
// GTAV B*1874706 - If the player is driving and trying to reverse but the impact is trying to push the car forward
|
||
|
// reduce the force of the impact.
|
||
|
if( pHitVehicle->IsDriverAPlayer() )
|
||
|
{
|
||
|
if( vDir.GetY() > 0.0f &&
|
||
|
pHitVehicle->GetThrottle() < -0.25f &&
|
||
|
pHitVehicle->GetVelocity().Mag2() < 5.0f )
|
||
|
{
|
||
|
fShootForce *= 1.0f - ( vDir.GetY() * 0.8f );
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Hack for B*7543674 .. dont allow breaking parts from patriot3
|
||
|
if( eFixedDamageType == DAMAGE_TYPE_BULLET && pHitVehicle->GetModelId() == MI_CAR_PATRIOT3 )
|
||
|
{
|
||
|
fShootForce = 0.0f;
|
||
|
}
|
||
|
|
||
|
pHitVehicle->ApplyExternalImpulse(fShootForce * vDir, RCC_VECTOR3(offsetFromInstance), pResult->GetHitComponent());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// CHEATS - FLAMING BULLETS - CHEAT
|
||
|
if(bPlayerResponsible)
|
||
|
{
|
||
|
CPed* pFiringPed = static_cast<CPed*>(pFiringEntity);
|
||
|
if(pFiringPed->IsLocalPlayer() && pFiringPed->GetPlayerInfo()->GetPlayerResetFlags().IsFlagSet(CPlayerResetFlags::PRF_FIRE_AMMO_ON) && eFixedDamageType == DAMAGE_TYPE_BULLET)
|
||
|
{
|
||
|
const float fBurnTime = fwRandom::GetRandomNumberInRange(20.0f, 35.0f);
|
||
|
const float fBurnStrength = fwRandom::GetRandomNumberInRange(0.75f, 1.0f);
|
||
|
const float fPeakTime = fwRandom::GetRandomNumberInRange(0.5f, 1.0f);
|
||
|
|
||
|
fragInstGta* pVehicleFragInst = pHitVehicle->GetVehicleFragInst();
|
||
|
if (pVehicleFragInst)
|
||
|
{
|
||
|
fragPhysicsLOD* pPhysicsLod = pVehicleFragInst->GetTypePhysics();
|
||
|
if (pPhysicsLod)
|
||
|
{
|
||
|
fragTypeChild* pFragTypeChild = pPhysicsLod->GetChild(pResult->GetHitComponent());
|
||
|
if (pFragTypeChild)
|
||
|
{
|
||
|
u16 boneId = pFragTypeChild->GetBoneID();
|
||
|
|
||
|
CVehicleModelInfo* pModelInfo = pHitVehicle->GetVehicleModelInfo();
|
||
|
if (pModelInfo)
|
||
|
{
|
||
|
gtaFragType* pFragType = pModelInfo->GetFragType();
|
||
|
if (pFragType)
|
||
|
{
|
||
|
s32 boneIndex = pFragType->GetBoneIndexFromID(boneId);
|
||
|
|
||
|
Vec3V vPosLcl;
|
||
|
Vec3V vNormLcl;
|
||
|
if (CVfxHelper::GetLocalEntityBonePosDir(*pHitVehicle, boneIndex, VECTOR3_TO_VEC3V(pResult->GetHitPosition()), VECTOR3_TO_VEC3V(vDir), vPosLcl, vNormLcl))
|
||
|
{
|
||
|
g_fireMan.StartVehicleFire(pHitVehicle, boneIndex, vPosLcl, vNormLcl, pFiringPed, fBurnTime, fBurnStrength, fPeakTime, FIRE_DEFAULT_NUM_GENERATIONS, Vec3V(V_ZERO), BANK_ONLY(Vec3V(V_ZERO),) false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return fDamageResult;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
f32 CWeaponDamage::DoWeaponImpactObject(CWeapon* pWeapon, CEntity* pFiringEntity, const Vector3& vWeaponPos, WorldProbe::CShapeTestHitPoint* pResult, const f32 fApplyDamage, const bool bTemporaryNetworkWeapon, const fwFlags32& flags, phMaterialMgr::Id hitMaterial)
|
||
|
{
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpactObject pWeapon[%p] pFiringEntity[%p] vWeaponPos[%f %f %f] pResult[%p] fApplyDamage[%f] bTemporaryNetworkWeapon[%d] flags[0x%x]",pWeapon,pFiringEntity,vWeaponPos.x,vWeaponPos.y,vWeaponPos.z,pResult,fApplyDamage,bTemporaryNetworkWeapon,(s32)flags);
|
||
|
|
||
|
CEntity* pHitEntity = CPhysics::GetEntityFromInst(pResult->GetHitInst());
|
||
|
weaponFatalAssertf(pHitEntity && pHitEntity->GetIsTypeObject(), "pHitEntity is NULL or not a Object");
|
||
|
CObject* pHitObj = static_cast<CObject*>(pHitEntity);
|
||
|
|
||
|
// Check for fragments that have broken apart since this impact was generated
|
||
|
fragInst* pFragInst = pHitObj->GetFragInst();
|
||
|
if(pFragInst && pFragInst->GetChildBroken(pResult->GetHitComponent()))
|
||
|
{
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpactObject--(pFragInst && pFragInst->GetChildBroken(pResult->GetHitComponent())) pHitEntity[%p][%s]-->return 0.0f",pHitEntity,pHitEntity ? pHitEntity->GetModelName() : "");
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
bool bCanDamageObject = true;
|
||
|
|
||
|
// A remote shot hitting a clone is not allowed to damage or move it. Also, remote shots are not permitted to start networking an object.
|
||
|
// If m_temporaryNetworkWeapon is set then this impact has been generated by a damage
|
||
|
// event from another machine and is therefore allowed to do the proper damage locally
|
||
|
if (pFiringEntity && NetworkUtils::IsNetworkClone(pFiringEntity) && !bTemporaryNetworkWeapon)
|
||
|
{
|
||
|
if( (pHitObj->GetNetworkObject() || CNetObjObject::HasToBeCloned(pHitObj)) && !pHitObj->IsADoor())
|
||
|
{
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpactObject--(pFiringEntity && NetworkUtils::IsNetworkClone(pFiringEntity) && !bTemporaryNetworkWeapon) pHitEntity[%p][%s]-->return 0.0f",pHitEntity,pHitEntity ? pHitEntity->GetModelName() : "");
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Remote script objects can never be damaged
|
||
|
if(NetworkUtils::IsNetworkCloneOrMigrating(pHitObj))
|
||
|
{
|
||
|
if (pHitObj->GetNetworkObject()->IsGlobalFlagSet(CNetObjGame::GLOBALFLAG_SCRIPTOBJECT))
|
||
|
{
|
||
|
//Dont allow weapon damage to script objects: B*1931368
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpactObject--IsNetworkCloneOrMigrating && (pHitObj->GetNetworkObject()->IsGlobalFlagSet(CNetObjGame::GLOBALFLAG_SCRIPTOBJECT)) pHitEntity[%p][%s]-->return 0.0f",pHitEntity,pHitEntity ? pHitEntity->GetModelName() : "");
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
bCanDamageObject = false;
|
||
|
}
|
||
|
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpactObject--bCanDamageObject[%d]",bCanDamageObject);
|
||
|
if(bCanDamageObject)
|
||
|
{
|
||
|
Vector3 vDamagePos = pResult->GetHitPosition();
|
||
|
Vector3 vDamageNorm = pResult->GetHitNormal();
|
||
|
|
||
|
if (!pHitObj->ObjectDamage(fApplyDamage, &vDamagePos, &vDamageNorm, pFiringEntity, pWeapon->GetWeaponHash(), hitMaterial))
|
||
|
{
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpactObject--pHitObj->ObjectDamage() returned false");
|
||
|
if( pHitObj->GetWeaponImpactsApplyGreaterForce() )
|
||
|
{
|
||
|
TUNE_GROUP_FLOAT( ARENA_MODE, sf_ScaleWeaponImpact, 8.0f, 0.0f, 25.0f, 0.1f );
|
||
|
bool isMeleeAttack = flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage );
|
||
|
f32 fShootForce = isMeleeAttack ? fApplyDamage * MELEE_WEAPON_FORCE_MULT : pWeapon->GetWeaponInfo()->GetForce();
|
||
|
fShootForce *= sf_ScaleWeaponImpact;
|
||
|
Vector3 vDir = pResult->GetHitPosition() - vWeaponPos;
|
||
|
vDir.Normalize();
|
||
|
f32 fFragImpulse = pWeapon->GetWeaponInfo()->GetFragImpulse();
|
||
|
|
||
|
pHitObj->ApplyExternalImpulse( fShootForce * vDir, pResult->GetHitPosition() - VEC3V_TO_VECTOR3( pHitObj->GetTransform().GetPosition() ), pResult->GetHitComponent(), pResult->GetHitPartIndex(), pResult->GetHitInst(), fFragImpulse, false );
|
||
|
}
|
||
|
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
DoWeaponImpactTrafficLight(pHitEntity, vDamagePos);
|
||
|
}
|
||
|
|
||
|
// Some pickups are collected by weapon impacts
|
||
|
if(pHitObj->m_nObjectFlags.bIsPickUp && pFiringEntity && static_cast<CPickup*>(pHitObj)->ProcessWeaponImpact(pFiringEntity))
|
||
|
{
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpactObject--(pHitObj->m_nObjectFlags.bIsPickUp && pFiringEntity && static_cast<CPickup*>(pHitObj)->ProcessWeaponImpact(pFiringEntity)) pHitEntity[%p][%s]-->return 0.0f",pHitEntity,pHitEntity ? pHitEntity->GetModelName() : "");
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
//----
|
||
|
|
||
|
// we're playing multiplayer...
|
||
|
if(NetworkInterface::IsGameInProgress())
|
||
|
{
|
||
|
// something shot an object...
|
||
|
if(pFiringEntity)
|
||
|
{
|
||
|
CBaseModelInfo const* pModelInfo = pHitObj->GetBaseModelInfo();
|
||
|
|
||
|
// and shot an object with glass in it....
|
||
|
if(pModelInfo->GetFragType() && pModelInfo->GetFragType()->GetNumGlassPaneModelInfos() > 0)
|
||
|
{
|
||
|
// and the firing object is local...
|
||
|
if(!NetworkUtils::IsNetworkClone(pFiringEntity))
|
||
|
{
|
||
|
// so we're taking control - it's up to us to flag how it gets damaged to the other machines so we registerWithNetwork
|
||
|
CGlassPaneManager::RegisterGlassGeometryObject_OnNetworkOwner( pHitObj, pResult->GetHitPosition(), (u8)pResult->GetHitComponent() );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//----
|
||
|
|
||
|
// Shot force is proportional to weapon damage
|
||
|
bool isMeleeAttack = flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage );
|
||
|
f32 fShootForce = isMeleeAttack ? fApplyDamage * MELEE_WEAPON_FORCE_MULT : pWeapon->GetWeaponInfo()->GetForce();
|
||
|
f32 fFragImpulse = pWeapon->GetWeaponInfo()->GetFragImpulse();
|
||
|
|
||
|
// Get the mass
|
||
|
f32 fHitMass = pHitObj->GetMass();
|
||
|
|
||
|
// If the other inst is a frag object (one with a tune file) then the prop
|
||
|
// artist might have set up this frag not to be broken/damaged/moved by melee attacks or weapons.
|
||
|
// Let's see if this is the case.
|
||
|
if(pHitObj->m_nFlags.bIsFrag)
|
||
|
{
|
||
|
if (!pFragInst->GetCached())
|
||
|
{
|
||
|
pFragInst->PutIntoCache();
|
||
|
}
|
||
|
|
||
|
fragTypeChild* pChild = pFragInst->GetTypePhysics()->GetChild(pResult->GetHitComponent());
|
||
|
int groupIndex = pChild->GetOwnerGroupPointerIndex();
|
||
|
fragTypeGroup* pGroup = pFragInst->GetTypePhysics()->GetGroup(groupIndex);
|
||
|
if (fragCacheEntry* pEntry = pFragInst->GetCacheEntry())
|
||
|
{
|
||
|
TrapLT(groupIndex, 0);
|
||
|
TrapGE(groupIndex, pFragInst->GetTypePhysics()->GetNumChildGroups());
|
||
|
float& weaponHealth = pEntry->GetHierInst()->groupInsts[groupIndex].weaponHealth;
|
||
|
weaponHealth -= fFragImpulse;
|
||
|
if (weaponHealth < 0.0f)
|
||
|
{
|
||
|
//
|
||
|
weaponHealth = 0.0f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Weapon health is not depleted, stop any breaking from occurring
|
||
|
fFragImpulse = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fFragImpulse *= isMeleeAttack ? pGroup->GetMeleeScale() : pGroup->GetWeaponScale();
|
||
|
|
||
|
// B*1848782: Make sure to apply the frag tuning ped inverse mass scales to melee attacks.
|
||
|
// This prevents objects that have been tuned to not move (or move less) due to peds from moving when hit by melee attacks.
|
||
|
if(pFiringEntity && pFiringEntity->GetType() == ENTITY_TYPE_PED && isMeleeAttack)
|
||
|
{
|
||
|
if(pGroup->GetPedInvMassScale() < 1.0f)
|
||
|
fShootForce *= pGroup->GetPedInvMassScale();
|
||
|
}
|
||
|
|
||
|
// Get the mass
|
||
|
fHitMass = pChild->GetUndamagedMass();
|
||
|
}
|
||
|
|
||
|
// Don't do any fragment breaking or damage if the object has been marked immune
|
||
|
if( (pHitObj->m_nPhysicalFlags.bNotDamagedByMelee && isMeleeAttack) ||
|
||
|
(pHitObj->m_nPhysicalFlags.bNotDamagedByBullets && !isMeleeAttack))
|
||
|
{
|
||
|
fFragImpulse = 0.0f;
|
||
|
}
|
||
|
|
||
|
static dev_float TEST_WEAPON_OBJECT_DELTAV_CAP = 10.0f;
|
||
|
if(fShootForce / fHitMass > TEST_WEAPON_OBJECT_DELTAV_CAP)
|
||
|
{
|
||
|
fShootForce = TEST_WEAPON_OBJECT_DELTAV_CAP * fHitMass;
|
||
|
}
|
||
|
|
||
|
// Apply force to object in original direction of bullet
|
||
|
Vector3 vDir = pResult->GetHitPosition() - vWeaponPos;
|
||
|
vDir.Normalize();
|
||
|
|
||
|
// Apply door weapon impulse modifier, if there is one
|
||
|
if (pHitObj->IsADoor())
|
||
|
{
|
||
|
const CDoor* pDoor = static_cast<CDoor*>(pHitObj);
|
||
|
|
||
|
// NOTE: NULL check is probably superfluous here
|
||
|
if (const CDoorTuning* pDoorTuning = pDoor->GetTuning())
|
||
|
{
|
||
|
fShootForce *= pDoorTuning->m_WeaponImpulseMultiplier;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// We had some issues where melee attacks could sometimes send a prop flying (chair, stool, etc) with
|
||
|
// a scenario ped sitting on it, at an unrealistic speed. Ideally, the physics system would take into
|
||
|
// account the mass of the ped that's attached to the prop, but since we probably have nothing like that,
|
||
|
// we try to scale down the force here instead, proportionally. This won't give us the right center of
|
||
|
// gravity and all that, but should still help quite a bit.
|
||
|
if(isMeleeAttack)
|
||
|
{
|
||
|
const float originalMass = pHitObj->GetMass();
|
||
|
float massWithAttachments = originalMass;
|
||
|
|
||
|
// Loop over the attachments.
|
||
|
const fwEntity* pChildAttachment = pHitObj->GetChildAttachment();
|
||
|
while(pChildAttachment)
|
||
|
{
|
||
|
if(static_cast<const CEntity*>(pChildAttachment)->GetIsTypePed())
|
||
|
{
|
||
|
// If this is a ped that's sitting...
|
||
|
const CPed& attachedPed = *static_cast<const CPed*>(pChildAttachment);
|
||
|
if(attachedPed.GetIsSitting())
|
||
|
{
|
||
|
// ... and is using a scenario point which is also attached to this entity...
|
||
|
const CScenarioPoint* pScenarioPt = CPed::GetScenarioPoint(attachedPed, true);
|
||
|
if(pScenarioPt && pScenarioPt->GetEntity() == pHitObj)
|
||
|
{
|
||
|
// ... add the mass of the ped.
|
||
|
massWithAttachments += attachedPed.GetMass();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pChildAttachment = pChildAttachment->GetSiblingAttachment();
|
||
|
}
|
||
|
|
||
|
// Recompute the force to apply, as if the mass of the prop had included
|
||
|
// the mass of its occupant(s).
|
||
|
const float forceScale = originalMass/massWithAttachments;
|
||
|
fShootForce *= forceScale;
|
||
|
}
|
||
|
|
||
|
if( pFiringEntity &&
|
||
|
pFiringEntity->GetIsTypePed() &&
|
||
|
pHitEntity->GetIsPhysical() )
|
||
|
{
|
||
|
CPed* pPed = static_cast<CPed*>(pFiringEntity);
|
||
|
|
||
|
if( pPed->GetGroundPhysical() == pHitEntity && !static_cast< CDynamicEntity* >(pHitEntity)->m_nDEflags.bIsBreakableGlass)
|
||
|
{
|
||
|
fShootForce = 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if( fShootForce > 0.0f )
|
||
|
{
|
||
|
if( pHitObj->GetWeaponImpactsApplyGreaterForce() )
|
||
|
{
|
||
|
static dev_float sf_ScaleWeaponImpact = 10.0f;
|
||
|
fShootForce *= sf_ScaleWeaponImpact;
|
||
|
}
|
||
|
pHitObj->ApplyExternalImpulse(fShootForce * vDir, pResult->GetHitPosition() - VEC3V_TO_VECTOR3( pHitObj->GetTransform().GetPosition()), pResult->GetHitComponent(), pResult->GetHitPartIndex(), pResult->GetHitInst(), fFragImpulse, false);
|
||
|
}
|
||
|
|
||
|
// Only tell network that object has moved, if it's actually become active
|
||
|
if(pHitObj->GetCurrentPhysicsInst()->IsInLevel() && CPhysics::GetLevel()->IsActive(pHitObj->GetCurrentPhysicsInst()->GetLevelIndex()))
|
||
|
{
|
||
|
pHitObj->ObjectHasBeenMoved(pFiringEntity);
|
||
|
}
|
||
|
|
||
|
if (pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pAttacker = static_cast<CPed*>(pFiringEntity);
|
||
|
const CAmmoInfo* pAmmoInfo = pWeapon->GetWeaponInfo() ? pWeapon->GetWeaponInfo()->GetAmmoInfo(pAttacker) : NULL;
|
||
|
if (pAmmoInfo && pAmmoInfo->GetIsIncendiary())
|
||
|
{
|
||
|
s32 boneIndex = -1;
|
||
|
if (pFragInst)
|
||
|
{
|
||
|
fragPhysicsLOD* pPhysicsLod = pFragInst->GetTypePhysics();
|
||
|
if (pPhysicsLod)
|
||
|
{
|
||
|
fragTypeChild* pFragTypeChild = pPhysicsLod->GetChild(pResult->GetHitComponent());
|
||
|
if (pFragTypeChild)
|
||
|
{
|
||
|
u16 boneId = pFragTypeChild->GetBoneID();
|
||
|
|
||
|
CBaseModelInfo* pModelInfo = pHitObj->GetBaseModelInfo();
|
||
|
if (pModelInfo)
|
||
|
{
|
||
|
gtaFragType* pFragType = pModelInfo->GetFragType();
|
||
|
if (pFragType)
|
||
|
{
|
||
|
boneIndex = pFragType->GetBoneIndexFromID(boneId);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Vec3V vPosLcl;
|
||
|
Vec3V vNormLcl;
|
||
|
if (CVfxHelper::GetLocalEntityBonePosDir(*pHitObj, boneIndex, pResult->GetHitPositionV(), pResult->GetHitNormalV(), vPosLcl, vNormLcl))
|
||
|
{
|
||
|
TUNE_GROUP_FLOAT(WEAPON_SPECIAL_AMMO, fFireStartChance, 0.25f, 0.0f, 1.f, 0.1f);
|
||
|
TUNE_GROUP_FLOAT(WEAPON_SPECIAL_AMMO, fFireBurnTime, 1.5f, 0.0f, 100.f, 0.1f);
|
||
|
TUNE_GROUP_FLOAT(WEAPON_SPECIAL_AMMO, fFireBurnStrength, 0.5f, 0.0f, 100.f, 0.1f);
|
||
|
TUNE_GROUP_FLOAT(WEAPON_SPECIAL_AMMO, fFireBurnPeakTime, 0.750f, 0.0f, 100.f, 0.1f);
|
||
|
if (fwRandom::GetRandomNumberInRange(0.0f, 1.0f) <= fFireStartChance)
|
||
|
g_fireMan.StartObjectFire(pHitObj, boneIndex, vPosLcl, vNormLcl, pAttacker, fFireBurnTime,fFireBurnStrength, fFireBurnPeakTime, FIRE_DEFAULT_NUM_GENERATIONS, pAttacker->GetTransform().GetPosition(), BANK_ONLY(pAttacker->GetTransform().GetPosition(),) false, pWeapon->GetWeaponHash());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpactObject-->return fApplyDamage[%f]",fApplyDamage);
|
||
|
return fApplyDamage;
|
||
|
}
|
||
|
|
||
|
void CWeaponDamage::DoWeaponImpactTrafficLight(CEntity* pHitEntity, const Vector3& vDamagePos)
|
||
|
{
|
||
|
CBaseModelInfo* pModelInfo = pHitEntity->GetBaseModelInfo();
|
||
|
if (pModelInfo->TestAttribute(MODEL_ATTRIBUTE_IS_TRAFFIC_LIGHT))
|
||
|
{
|
||
|
// see if we hit one of the lights and if so, disable it.
|
||
|
const BaseModelInfoBoneIndices* pExtension = CTrafficLights::GetExtension(pModelInfo);
|
||
|
if(pExtension == NULL )
|
||
|
return;
|
||
|
|
||
|
Mat34V matrix;
|
||
|
int boneIdx;
|
||
|
|
||
|
for (u8 boneId = TRAFFIC_LIGHT_0; boneId < TRAFFIC_LIGHT_COUNT; boneId++)
|
||
|
{
|
||
|
boneIdx = pExtension->GetBoneIndice(eTrafficLightComponentId(boneId));
|
||
|
if (boneIdx == 0xff)
|
||
|
continue;
|
||
|
|
||
|
CVfxHelper::GetMatrixFromBoneIndex_Lights(matrix, pHitEntity, boneIdx);
|
||
|
|
||
|
Vector3 boneLocation = VEC3V_TO_VECTOR3(matrix.d());
|
||
|
f32 distance = fabs((boneLocation - vDamagePos).Mag());
|
||
|
|
||
|
// if we're checking against a ped walk box, we don't need to loop through any other bones/offsets
|
||
|
if (boneId == PED_WALK_BOX &&
|
||
|
distance < TRAFFIC_LIGHT_PED_BOX_DAMAGE_RADIUS)
|
||
|
{
|
||
|
TrafficLightInfos *tli = TrafficLightInfos::Get(pHitEntity);
|
||
|
|
||
|
if( NULL == tli )
|
||
|
{
|
||
|
tli = TrafficLightInfos::Add(pHitEntity);
|
||
|
}
|
||
|
|
||
|
// TrafficLightInfos::Add can return NULL, if its pool is full.
|
||
|
if( tli )
|
||
|
{
|
||
|
tli->DamageLight(boneId, 0);
|
||
|
}
|
||
|
}
|
||
|
else if (distance < TRAFFIC_LIGHT_CORONA_BULLET_DAMAGE_RADIUS)
|
||
|
{
|
||
|
// the traffic lights are a lot taller than they are wide, so just make sure the x/y distance isn't too far off
|
||
|
if (fabs(boneLocation.x - vDamagePos.x) < TRAFFIC_LIGHT_CORONA_VERTICLE_OFFSET &&
|
||
|
fabs(boneLocation.y - vDamagePos.y) < TRAFFIC_LIGHT_CORONA_VERTICLE_OFFSET)
|
||
|
{
|
||
|
TrafficLightInfos *tli = TrafficLightInfos::Get(pHitEntity);
|
||
|
|
||
|
if( NULL == tli )
|
||
|
{
|
||
|
tli = TrafficLightInfos::Add(pHitEntity);
|
||
|
}
|
||
|
|
||
|
// TrafficLightInfos::Add can return NULL, if its pool is full.
|
||
|
if( tli )
|
||
|
{
|
||
|
// we know we are damaging this light, we just need to figure out if the bullet hit closest to
|
||
|
// the red, amber, or orange light so we know which one to damage.
|
||
|
static float lightOffsets[] = { -TRAFFIC_LIGHT_CORONA_VERTICLE_OFFSET, 0.0f, TRAFFIC_LIGHT_CORONA_VERTICLE_OFFSET };
|
||
|
|
||
|
u8 lightId;
|
||
|
for (lightId = LIGHT_GREEN; lightId < LIGHT_OFF; lightId++)
|
||
|
{
|
||
|
const ScalarV offset = ScalarVFromF32(lightOffsets[lightId]);
|
||
|
Vector3 lightPos = boneLocation + VEC3V_TO_VECTOR3(matrix.GetCol2()*offset);
|
||
|
f32 distance = fabs((lightPos - vDamagePos).Mag());
|
||
|
|
||
|
if (distance <= TRAFFIC_LIGHT_HALF_DAMAGE_RADIUS)
|
||
|
{
|
||
|
tli->DamageLight(boneId, lightId);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void CWeaponDamage::DoWeaponImpactSearchLight(CEntity* pHitEntity, const Vector3& vDamagePos)
|
||
|
{
|
||
|
CSearchLight* pSearchLight = NULL;
|
||
|
CVehicle* pHitVehicle = static_cast<CVehicle*>(pHitEntity);
|
||
|
|
||
|
if(pHitVehicle->InheritsFromHeli())
|
||
|
{
|
||
|
pSearchLight = static_cast<CRotaryWingAircraft*>(pHitVehicle)->GetSearchLight();
|
||
|
}
|
||
|
else if(pHitVehicle->InheritsFromBoat())
|
||
|
{
|
||
|
pSearchLight = static_cast<CBoat*>(pHitVehicle)->GetSearchLight();
|
||
|
}
|
||
|
|
||
|
if(pSearchLight != NULL)
|
||
|
{
|
||
|
const SearchLightInfo& slInfo = CSearchLight::GetSearchLightInfo(pHitVehicle);
|
||
|
|
||
|
int boneIdx = pHitVehicle->GetBoneIndex(pSearchLight->GetWeaponBone());
|
||
|
if(boneIdx == 0xFF)
|
||
|
return;
|
||
|
|
||
|
Matrix34 matrix;
|
||
|
pHitVehicle->GetGlobalMtx(boneIdx, matrix);
|
||
|
|
||
|
Vector3 lightPos = matrix.d;
|
||
|
Vector3 lightDir = matrix.b;
|
||
|
Vector3 boneLocation = lightPos+ (lightDir * slInfo.offset);
|
||
|
|
||
|
f32 distance = fabs((boneLocation - vDamagePos).Mag());
|
||
|
|
||
|
if (distance < TRAFFIC_LIGHT_CORONA_BULLET_DAMAGE_RADIUS)
|
||
|
{
|
||
|
pSearchLight->SetLightOn(false);
|
||
|
pSearchLight->SetIsDamaged();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
f32 CWeaponDamage::DoWeaponImpactWeapon(CWeapon* pWeapon, CEntity* pFiringEntity, const Vector3& vWeaponPos, WorldProbe::CShapeTestHitPoint* pResult, const f32 fApplyDamage, const bool bTemporaryNetworkWeapon, const fwFlags32& flags, const sMeleeInfo *pMeleeInfo /*= NULL*/, NetworkWeaponImpactInfo * const networkWeaponImpactInfo /* = NULL */, const float fRecoilAccuracyWhenFired /* = 1.f */, u8 damageAggregationCount /*= 0*/)
|
||
|
{
|
||
|
// clear if we're setting information on the owner and not using it on the clone...
|
||
|
if(networkWeaponImpactInfo)
|
||
|
{
|
||
|
networkWeaponImpactInfo->Clear();
|
||
|
}
|
||
|
|
||
|
CEntity* pHitEntity = CPhysics::GetEntityFromInst(pResult->GetHitInst());
|
||
|
//Check if we've lost the hitinstance, this is possible with frag objects.
|
||
|
if(!pHitEntity)
|
||
|
{
|
||
|
return 0.0f;
|
||
|
}
|
||
|
weaponFatalAssertf(pHitEntity->GetIsTypeObject(), "pHitEntity is not a Object");
|
||
|
CObject* pHitObj = static_cast<CObject*>(pHitEntity);
|
||
|
|
||
|
CEntity* pHitAttachParent = (CPhysical *) pHitObj->GetAttachParent();
|
||
|
|
||
|
// we've hit an ammo box attached to the gun...
|
||
|
if(pHitAttachParent && !pHitAttachParent->GetIsTypePed())
|
||
|
{
|
||
|
if(pHitAttachParent->GetIsTypeObject())
|
||
|
{
|
||
|
if(((CObject*)pHitAttachParent)->GetWeapon())
|
||
|
{
|
||
|
pHitAttachParent = (CPhysical *)pHitAttachParent->GetAttachParent();
|
||
|
|
||
|
if(networkWeaponImpactInfo)
|
||
|
networkWeaponImpactInfo->SetAmmoAttachment( true );
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(pHitAttachParent && pHitAttachParent->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pHitPed = static_cast<CPed*>(pHitAttachParent);
|
||
|
|
||
|
if(!NetworkUtils::IsNetworkCloneOrMigrating(pHitPed))
|
||
|
{
|
||
|
if(!pHitPed->IsAPlayerPed())
|
||
|
{
|
||
|
const CPed* pFiringPlayerPed = pFiringEntity && pFiringEntity->GetIsTypePed() && static_cast<const CPed*>(pFiringEntity)->IsAPlayerPed() ? static_cast<const CPed*>(pFiringEntity) : NULL;
|
||
|
|
||
|
if(!pHitPed->PopTypeIsMission() && pFiringPlayerPed)
|
||
|
{
|
||
|
// Save the offset before TurnPedsObjectIntoPickup is called, in case that moves the object
|
||
|
Vector3 vHitOffset(pResult->GetHitPosition());
|
||
|
vHitOffset.Subtract(VEC3V_TO_VECTOR3(pHitObj->GetTransform().GetPosition()));
|
||
|
|
||
|
if(pWeapon)
|
||
|
{
|
||
|
const CWeapon* pHitWeapon = pHitObj->GetWeapon();
|
||
|
if(pHitWeapon)
|
||
|
{
|
||
|
//Check if the weapon can be dropped.
|
||
|
if(CanDropWeaponWhenShot(*pWeapon, *pHitPed, *pHitWeapon) && pFiringPlayerPed->IsAllowedToDamageEntity(pWeapon->GetWeaponInfo(), pHitPed))
|
||
|
{
|
||
|
CPickup* pPickup = CPickupManager::CreatePickUpFromCurrentWeapon(pHitPed);
|
||
|
if(pPickup)
|
||
|
{
|
||
|
// Remove from peds inventory
|
||
|
if(pHitPed->GetInventory())
|
||
|
{
|
||
|
pHitPed->GetInventory()->RemoveWeapon(pHitObj->GetWeapon()->GetWeaponHash());
|
||
|
}
|
||
|
|
||
|
Vector3 vDir(pResult->GetHitPosition() - vWeaponPos);
|
||
|
vDir.NormalizeFast();
|
||
|
|
||
|
static dev_float fImpulseScale = 0.01f;
|
||
|
vDir *= pPickup->GetMass() * fImpulseScale;
|
||
|
|
||
|
if(pPickup->GetCurrentPhysicsInst() && pPickup->GetCurrentPhysicsInst()->IsInLevel())
|
||
|
{
|
||
|
pPickup->ApplyImpulse(vDir, vHitOffset, pResult->GetHitComponent());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Ignore the impact to the ped if the object can shoot through.
|
||
|
if(!pHitObj->CanShootThroughWhenEquipped())
|
||
|
{
|
||
|
float fDamage = 0.0f;
|
||
|
|
||
|
//Check if the bullet will go through the weapon and damage the ped?
|
||
|
Vector3 vDir(pResult->GetHitPosition() - vWeaponPos);
|
||
|
vDir.NormalizeFast();
|
||
|
WorldProbe::CShapeTestProbeDesc probeData;
|
||
|
WorldProbe::CShapeTestFixedResults<1> ShapeTestResults; // We're only looking at the first result, so requesting more is wasteful
|
||
|
probeData.SetResultsStructure(&ShapeTestResults);
|
||
|
probeData.SetStartAndEnd(pResult->GetHitPosition(), pResult->GetHitPosition() + vDir);
|
||
|
probeData.SetExcludeInstance(pHitPed->GetAnimatedInst());
|
||
|
probeData.SetIncludeFlags(ArchetypeFlags::GTA_RAGDOLL_TYPE);
|
||
|
probeData.SetStateIncludeFlags(phLevelBase::STATE_FLAGS_ALL);
|
||
|
probeData.SetIsDirected(true);
|
||
|
if(WorldProbe::GetShapeTestManager()->SubmitTest(probeData))
|
||
|
{
|
||
|
fDamage = fApplyDamage;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Test for the closest component of the ped's ragdoll to the shot point (within SPHERE_RADIUS). Make sure we exclude the animation
|
||
|
// instance from the collision test; it generates spurious components otherwise.
|
||
|
static dev_float SPHERE_RADIUS = 1.0f;
|
||
|
WorldProbe::CShapeTestSphereDesc sphereDesc;
|
||
|
sphereDesc.SetResultsStructure(&ShapeTestResults);
|
||
|
sphereDesc.SetSphere(pResult->GetHitPosition(), SPHERE_RADIUS);
|
||
|
sphereDesc.SetExcludeInstance(pHitPed->GetAnimatedInst());
|
||
|
sphereDesc.SetIncludeFlags(ArchetypeFlags::GTA_RAGDOLL_TYPE);
|
||
|
sphereDesc.SetTypeFlags(TYPE_FLAGS_ALL);
|
||
|
sphereDesc.SetStateIncludeFlags(phLevelBase::STATE_FLAGS_ALL);
|
||
|
|
||
|
if(!WorldProbe::GetShapeTestManager()->SubmitTest(sphereDesc))
|
||
|
{
|
||
|
return 0.0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CEntity* pNewHitEntity = CPhysics::GetEntityFromInst(ShapeTestResults[0].GetHitInst());
|
||
|
if(pNewHitEntity == pHitPed)
|
||
|
{
|
||
|
bool blockDamage = pFiringEntity && ((pFiringEntity->GetIsTypePed() && !static_cast<CPed*>(pFiringEntity)->IsAllowedToDamageEntity(pWeapon->GetWeaponInfo(), pNewHitEntity))
|
||
|
|| (pFiringEntity->GetIsTypeVehicle() && static_cast<CVehicle*>(pFiringEntity)->GetDriver() && !static_cast<CVehicle*>(pFiringEntity)->GetDriver()->IsAllowedToDamageEntity(pWeapon->GetWeaponInfo(), pNewHitEntity)));
|
||
|
|
||
|
if(!blockDamage)
|
||
|
{
|
||
|
// set the flag to say we've also hit the ped if we're setting information on the owner and not using it on the clone...
|
||
|
if(networkWeaponImpactInfo)
|
||
|
{
|
||
|
networkWeaponImpactInfo->SetDamagedHoldingPed( true );
|
||
|
}
|
||
|
|
||
|
return DoWeaponImpactPed(pWeapon, pFiringEntity, vWeaponPos, &ShapeTestResults[0], fDamage, bTemporaryNetworkWeapon, flags, pMeleeInfo, fRecoilAccuracyWhenFired, NULL, damageAggregationCount);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0.0f;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
f32 CWeaponDamage::DoWeaponImpactBreakableGlass(CWeapon* pWeapon, CEntity* UNUSED_PARAM(pFiringEntity), const Vector3& vWeaponPos, WorldProbe::CShapeTestHitPoint* pResult, const f32 fApplyDamage)
|
||
|
{
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpactBreakableGlass");
|
||
|
|
||
|
bool bIsDamageMelee = pWeapon->GetWeaponInfo()->GetDamageType() == DAMAGE_TYPE_MELEE;
|
||
|
f32 fShootForce = bIsDamageMelee ? fApplyDamage * MELEE_WEAPON_FORCE_MULT : pWeapon->GetWeaponInfo()->GetForce();
|
||
|
f32 fFragImpulse = pWeapon->GetWeaponInfo()->GetFragImpulse();
|
||
|
|
||
|
Vector3 vDir = pResult->GetHitPosition() - vWeaponPos;
|
||
|
vDir.Normalize();
|
||
|
|
||
|
PHSIM->ApplyImpetusAndBreakingImpetus(
|
||
|
ScalarV(fwTimer::GetTimeStep() / CPhysics::GetNumTimeSlices()).GetIntrin128ConstRef(),
|
||
|
pResult->GetHitInst()->GetLevelIndex(),
|
||
|
fShootForce * vDir,
|
||
|
pResult->GetHitPosition(),
|
||
|
pResult->GetHitComponent(),
|
||
|
pResult->GetHitPartIndex(),
|
||
|
fFragImpulse
|
||
|
);
|
||
|
|
||
|
return fApplyDamage;
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void CWeaponDamage::DoWeaponImpactAI(CWeapon* pWeapon, CEntity* pFiringEntity, const Vector3& vWeaponPos, WorldProbe::CShapeTestHitPoint* pResult, const f32 UNUSED_PARAM(fDamageDone))
|
||
|
{
|
||
|
const CWeaponInfo* pWeaponInfo = pWeapon->GetWeaponInfo();
|
||
|
// Adding a check against if the gun is lethal or not - don't want peds overreacting to things like stunguns
|
||
|
if(pWeaponInfo->GetIsGun() && !pWeaponInfo->GetIsNonLethal() && (pWeaponInfo->GetFireType() == FIRE_TYPE_INSTANT_HIT || pWeaponInfo->GetFireType() == FIRE_TYPE_DELAYED_HIT))
|
||
|
{
|
||
|
// Create a bullet impact for this event.
|
||
|
bool bSilentGunshot = pWeapon->GetIsSilenced();
|
||
|
CPed* pFiringPed = (pFiringEntity && pFiringEntity->GetIsTypePed()) ? static_cast<CPed*>(pFiringEntity) : NULL;
|
||
|
bool bBlockFiringEvents = (pFiringPed && pFiringPed->GetPedResetFlag(CPED_RESET_FLAG_SupressGunfireEvents));
|
||
|
if( !bBlockFiringEvents )
|
||
|
{
|
||
|
CEventGunShotBulletImpact eventGunShotBulletImpact(pFiringEntity, vWeaponPos, pResult->GetHitPosition(), bSilentGunshot, pWeapon->GetWeaponHash());
|
||
|
GetEventGlobalGroup()->Add(eventGunShotBulletImpact);
|
||
|
|
||
|
// Intended for friendly bullet impact events
|
||
|
CEventFriendlyFireNearMiss eventFriendlyNearMiss(pFiringEntity, vWeaponPos, pResult->GetHitPosition(), bSilentGunshot, pWeapon->GetWeaponHash(), CEventFriendlyFireNearMiss::FNM_BULLET_IMPACT);
|
||
|
GetEventGlobalGroup()->Add(eventFriendlyNearMiss);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void CWeaponDamage::DoWeaponImpactVfx(CWeapon* pWeapon, CEntity* pFiringEntity, const Vector3& vWeaponPos, WorldProbe::CShapeTestHitPoint* pResult, const f32 fDamageDone, const fwFlags32& flags, const bool headShotNearby, const bool isExitFx, const bool bTemporaryNetworkWeapon /* = false */, const bool bShouldApplyDamageToGlass /* = true */)
|
||
|
{
|
||
|
#if !__NO_OUTPUT
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpactVfx pWeapon[%p] pFiringEntity[%p] vWeaponPos[%f %f %f] pResult[%p] fDamageDone[%f] headShotNearby[%d] isExitFx[%d] bTemporaryNetworkWeapon[%d]",pWeapon,pFiringEntity,vWeaponPos.x,vWeaponPos.y,vWeaponPos.z,pResult,fDamageDone,headShotNearby,isExitFx,bTemporaryNetworkWeapon);
|
||
|
#endif
|
||
|
|
||
|
#if __BANK
|
||
|
if(!CPhysics::ms_bDoWeaponImpactEffects)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
#endif // __BANK
|
||
|
|
||
|
#if __ASSERT
|
||
|
if( !Verifyf(pResult, "No shapetest result passed in to CWeaponDamage::DoWeaponImpactVfx.") ||
|
||
|
!Verifyf(pResult->GetHitDetected(), "No hit on impact passed into CWeaponDamage::DoWeaponImpactVfx.") ||
|
||
|
!Verifyf(pResult->GetHitInst(), "No instance on impact passed into CWeaponDamage::DoWeaponImpactVfx.") ||
|
||
|
!Verifyf(pResult->GetHitInst()->IsInLevel(), "Instance on impact passed into CWeaponDamage::DoWeaponImpactVfx doesn't exist in level."))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if !__NO_OUTPUT
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpactVfx -- pResult[%p] GetHitDetected[%d] GetHitInst[%p] IsInLevel[%d]",pResult,pResult ? pResult->GetHitDetected() : false,pResult ? pResult->GetHitInst() : 0,pResult && pResult->GetHitInst() ? pResult->GetHitInst()->IsInLevel() : 0);
|
||
|
#endif
|
||
|
|
||
|
// get the collision position
|
||
|
Vec3V vColnPos = RCC_VEC3V(pResult->GetHitPosition());
|
||
|
|
||
|
// add smoke if we're in an interior
|
||
|
if (pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pFiringPed = static_cast<CPed*>(pFiringEntity);
|
||
|
CObject* pWeaponObj = pFiringPed->GetWeaponManager() ? pFiringPed->GetWeaponManager()->GetEquippedWeaponObject() : NULL;
|
||
|
if (pWeaponObj)
|
||
|
{
|
||
|
CPortalTracker* pPortalTracker = pWeaponObj->GetPortalTracker();
|
||
|
if(pPortalTracker)
|
||
|
{
|
||
|
CInteriorInst* pIntInst = pPortalTracker->GetInteriorInst();
|
||
|
if(pIntInst && pPortalTracker->m_roomIdx > 0)
|
||
|
{
|
||
|
// We're in a room inside an interior
|
||
|
pIntInst->AddSmokeToRoom(pPortalTracker->m_roomIdx, RCC_VECTOR3(vColnPos));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool bHitGlass = false;
|
||
|
|
||
|
// deal with effects when we need to have a valid entity from the inst
|
||
|
CEntity* pHitEntity = CPhysics::GetEntityFromInst(pResult->GetHitInst());
|
||
|
if (pHitEntity)
|
||
|
{
|
||
|
// set up the collision info structure
|
||
|
VfxCollInfo_s vfxCollInfo;
|
||
|
vfxCollInfo.regdEnt = pHitEntity;
|
||
|
vfxCollInfo.vPositionWld = RCC_VEC3V(pResult->GetHitPosition());
|
||
|
vfxCollInfo.vNormalWld = RCC_VEC3V(pResult->GetHitNormal());
|
||
|
|
||
|
if (pWeapon->GetWeaponInfo()->GetIsMelee())
|
||
|
{
|
||
|
// the normal approximates the direction of the blow
|
||
|
vfxCollInfo.vDirectionWld = vfxCollInfo.vNormalWld;
|
||
|
vfxCollInfo.vNormalWld = -vfxCollInfo.vNormalWld;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// calc the direction of the gun shot
|
||
|
vfxCollInfo.vDirectionWld = vfxCollInfo.vPositionWld - RCC_VEC3V(vWeaponPos);
|
||
|
}
|
||
|
|
||
|
vfxCollInfo.vDirectionWld = Normalize(vfxCollInfo.vDirectionWld);
|
||
|
vfxCollInfo.materialId = PGTAMATERIALMGR->UnpackMtlId(pResult->GetHitMaterialId());
|
||
|
vfxCollInfo.roomId = PGTAMATERIALMGR->UnpackRoomId(pResult->GetHitMaterialId());
|
||
|
vfxCollInfo.componentId = pResult->GetHitComponent();
|
||
|
vfxCollInfo.weaponGroup = pWeapon->GetWeaponInfo()->GetEffectGroup();
|
||
|
vfxCollInfo.force = VEHICLEGLASSFORCE_WEAPON_IMPACT;
|
||
|
vfxCollInfo.isBloody = headShotNearby;
|
||
|
vfxCollInfo.isWater = false;
|
||
|
vfxCollInfo.isExitFx = isExitFx;
|
||
|
vfxCollInfo.noPtFx = false;
|
||
|
vfxCollInfo.noPedDamage = false;
|
||
|
vfxCollInfo.noDecal = PGTAMATERIALMGR->GetIsNoDecal(pResult->GetHitMaterialId()) ||
|
||
|
flags.IsFlagSet( CPedDamageCalculator::DF_PtFxOnly );
|
||
|
vfxCollInfo.isSnowball = false;
|
||
|
|
||
|
if (pWeapon->GetWeaponInfo())
|
||
|
{
|
||
|
float fWeaponDamage = CWeaponInfoManager::GetArmouredGlassDamageForWeaponGroup(pWeapon->GetWeaponInfo()->GetGroup());
|
||
|
|
||
|
// Apply weapon-specific damage override if defined.
|
||
|
float fOverridenDamage = pWeapon->GetWeaponInfo()->GetArmouredVehicleGlassDamageOverride();
|
||
|
if (fOverridenDamage != -1.0f)
|
||
|
{
|
||
|
fWeaponDamage = fOverridenDamage;
|
||
|
}
|
||
|
|
||
|
// Spread damage out per-bullet (ie shotguns).
|
||
|
vfxCollInfo.armouredGlassWeaponDamage = (fWeaponDamage / pWeapon->GetBulletsInBatch());
|
||
|
|
||
|
vfxCollInfo.armouredGlassShotByPedInsideVehicle = false;
|
||
|
// Flag if ped is shooting in FPS mode from inside it's own vehicle.
|
||
|
TUNE_GROUP_BOOL(VEH_GLASS_HACKS, bInstantSmashBulletResistantGlassWhenInside, true);
|
||
|
if (bInstantSmashBulletResistantGlassWhenInside && pFiringEntity && pFiringEntity->GetIsTypePed() && pHitEntity && pHitEntity->GetIsTypeVehicle())
|
||
|
{
|
||
|
CPed* pFiringPed = static_cast<CPed*>(pFiringEntity);
|
||
|
if (pFiringPed && pFiringPed->GetIsInVehicle())
|
||
|
{
|
||
|
CVehicle *pVehicle = pFiringPed->GetVehiclePedInside();
|
||
|
CVehicle *pHitVehicle = static_cast<CVehicle*>(pHitEntity);
|
||
|
if (pVehicle && pHitVehicle && pVehicle == pHitVehicle)
|
||
|
{
|
||
|
vfxCollInfo.armouredGlassShotByPedInsideVehicle = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Flag if Full Metal Jacket ammo
|
||
|
vfxCollInfo.isFMJAmmo = false;
|
||
|
if (pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pFiringPed = static_cast<CPed*>(pFiringEntity);
|
||
|
const CAmmoInfo* pAmmoInfo = pWeapon->GetWeaponInfo()->GetAmmoInfo(pFiringPed);
|
||
|
if (pAmmoInfo && pAmmoInfo->GetIsFMJ())
|
||
|
{
|
||
|
vfxCollInfo.isFMJAmmo = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// deal with using 'non-melee' weapons for melee attacks
|
||
|
if (flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) && vfxCollInfo.weaponGroup>WEAPON_EFFECT_GROUP_MELEE_GENERIC)
|
||
|
{
|
||
|
vfxCollInfo.weaponGroup = WEAPON_EFFECT_GROUP_MELEE_METAL;
|
||
|
}
|
||
|
|
||
|
#if !__NO_OUTPUT
|
||
|
const phMaterial &mat = PGTAMATERIALMGR->GetMaterial(vfxCollInfo.materialId);
|
||
|
weaponDebugf3("vfxCollInfo regdEnt[%p] vPositionWld[%f %f %f] materialId[%" I64FMT "d][%s] componentId[%d] weaponGroup[%d] force[%f] isBloody[%d] isExitFx[%d] noDecal[%d]",pHitEntity,pResult->GetHitPosition().x,pResult->GetHitPosition().y,pResult->GetHitPosition().z,vfxCollInfo.materialId,mat.GetName(),vfxCollInfo.componentId,vfxCollInfo.weaponGroup,vfxCollInfo.force,vfxCollInfo.isBloody,vfxCollInfo.isExitFx,vfxCollInfo.noDecal);
|
||
|
#endif
|
||
|
|
||
|
|
||
|
if (vfxCollInfo.regdEnt && vfxCollInfo.regdEnt->GetIsTypeObject())
|
||
|
{
|
||
|
// NOTE: pure cloth ( there are no cloth and non-cloth parts in the same frag) impact is not processed here anymore
|
||
|
// see CBullet::ComputeImpacts
|
||
|
// Svetli
|
||
|
fragInst* pFragInst = pHitEntity->GetFragInst();
|
||
|
bool bOnlyCloth = false;
|
||
|
if( pFragInst && pFragInst->GetType() && (pFragInst->GetType()->GetNumEnvCloths() > 0) )
|
||
|
{
|
||
|
bOnlyCloth = (NULL == pFragInst->GetType()->GetClothDrawable()) ;
|
||
|
}
|
||
|
if( bOnlyCloth )
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bHitGlass = vfxCollInfo.regdEnt && PGTAMATERIALMGR->GetIsSmashableGlass(pResult->GetHitMaterialId()) && IsEntitySmashable(vfxCollInfo.regdEnt);
|
||
|
|
||
|
// Process the visual effects
|
||
|
if (!flags.IsFlagSet( CPedDamageCalculator::DF_PtFxOnly ) && bHitGlass)
|
||
|
{
|
||
|
// Check if we want to process the glass smashing
|
||
|
bool bProcessGlassSmashing = bShouldApplyDamageToGlass;
|
||
|
|
||
|
if (CVfxHelper::GetDistSqrToCamera(vfxCollInfo.vPositionWld)>VEHICLE_GLASS_BREAK_RANGE_SQR)
|
||
|
{
|
||
|
bProcessGlassSmashing = false;
|
||
|
}
|
||
|
|
||
|
// MN - removed this for url:bugstar:1182317
|
||
|
// don't really see why we'd ever want to stop glass smashing or taking decals when this flag is set
|
||
|
// /* if (pWeapon->GetDamageType()==DAMAGE_TYPE_ELECTRIC)
|
||
|
// {
|
||
|
// // electric weapons (e.g. stun guns) don't want to cause glass to smash
|
||
|
// bProcessGlassSmashing = false;
|
||
|
// }
|
||
|
// else*/ if (vfxCollInfo.regdEnt->GetIsPhysical())
|
||
|
// {
|
||
|
// CPhysical* pPhysical = static_cast<CPhysical*>(vfxCollInfo.regdEnt.Get());
|
||
|
// if (pPhysical->m_nPhysicalFlags.bNotDamagedByBullets)
|
||
|
// {
|
||
|
// bProcessGlassSmashing = false;
|
||
|
// }
|
||
|
// }
|
||
|
|
||
|
// Try to smash glass if required
|
||
|
if (bProcessGlassSmashing)
|
||
|
{
|
||
|
// disable particle effects from peds shooting their own vehicle when drive-by shooting
|
||
|
// this mainly stops lots of windscreen glass bullet impact effects being created
|
||
|
if (pHitEntity->GetIsTypeVehicle() && pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
CVehicle* pHitVehicle = static_cast<CVehicle*>(pHitEntity);
|
||
|
CPed* pFiringPed = static_cast<CPed*>(pFiringEntity);
|
||
|
if (pFiringPed->GetVehiclePedInside()==pHitVehicle)
|
||
|
{
|
||
|
vfxCollInfo.noPtFx = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
vfxCollInfo.isSnowball = pWeapon->GetWeaponInfo()->GetHash()==WEAPONTYPE_DLC_SNOWBALL;
|
||
|
|
||
|
// Glass collision on a smashable entity
|
||
|
g_vehicleGlassMan.StoreCollision(vfxCollInfo);
|
||
|
}
|
||
|
}
|
||
|
else if (pHitEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pPed = static_cast<CPed*>(pHitEntity);
|
||
|
|
||
|
if (weaponVerifyf(PGTAMATERIALMGR->GetIsPed(pResult->GetHitMaterialId()), "shot a ped that isn't set up with ped materials (%s - %" I64FMT "u)", pHitEntity->GetModelName(), pResult->GetHitMaterialId()))
|
||
|
{
|
||
|
bool bShouldShowVFX = fDamageDone > 0.0f || IsEnduranceDamage(pPed, pWeapon, flags);
|
||
|
|
||
|
if (bShouldShowVFX)
|
||
|
{
|
||
|
if (pFiringEntity && NetworkUtils::IsNetworkCloneOrMigrating(pFiringEntity) && !NetworkUtils::IsNetworkCloneOrMigrating(pHitEntity) && !bTemporaryNetworkWeapon)
|
||
|
{
|
||
|
// if the firing entity is a clone and the hit entity isn't,
|
||
|
// I'm going to receive a message over the network from the firing entity owner telling me to apply damage
|
||
|
// in that case, stop the blood being added twice
|
||
|
// and only use the network event which should contain better / closer data
|
||
|
// as the ped firing is randomised based on accuracy and gun firing cone dimensions
|
||
|
|
||
|
vfxCollInfo.noPedDamage = true;
|
||
|
}
|
||
|
|
||
|
// play blood vfx
|
||
|
g_vfxBlood.DoVfxBlood(vfxCollInfo, pFiringEntity);
|
||
|
|
||
|
// play shot vfx
|
||
|
g_vfxPed.TriggerPtFxShot(pPed, vfxCollInfo.vPositionWld, vfxCollInfo.vNormalWld);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// play weapon impact vfx
|
||
|
g_vfxWeapon.DoWeaponImpactVfx(vfxCollInfo, pWeapon->GetDamageType(), pFiringEntity);
|
||
|
}
|
||
|
|
||
|
if (NetworkInterface::IsGameInProgress() && !vfxCollInfo.noDecal && pResult && !bHitGlass)
|
||
|
{
|
||
|
//Record the weapon impact points in areas for vehicles so we can replicate/fake-out the impacts onto remotes when they come into scope
|
||
|
if (pHitEntity->GetIsTypeVehicle())
|
||
|
{
|
||
|
CVehicle* pVehicle = static_cast<CVehicle*>(pHitEntity);
|
||
|
if (pVehicle && pVehicle->GetNetworkObject())
|
||
|
{
|
||
|
CNetObjVehicle* pNetObjVehicle = static_cast<CNetObjVehicle*>(pVehicle->GetNetworkObject());
|
||
|
if (pNetObjVehicle)
|
||
|
{
|
||
|
pNetObjVehicle->StoreWeaponImpactPointInformation(pResult->GetHitPosition(), vfxCollInfo.weaponGroup, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void CWeaponDamage::DoWeaponImpactAudio(CWeapon* pWeapon, CEntity* pFiringEntity, const Vector3& vWeaponPos, WorldProbe::CShapeTestHitPoint* pResult, const f32 fDamageDone, const fwFlags32& flags)
|
||
|
{
|
||
|
weaponDebugf3("CWeaponDamage::DoWeaponImpactAudio");
|
||
|
|
||
|
bool playerShot = false;
|
||
|
CPed* pPedFiringEntity = NULL;
|
||
|
if(pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
pPedFiringEntity = static_cast<CPed*>(pFiringEntity);
|
||
|
if (pPedFiringEntity)
|
||
|
playerShot = pPedFiringEntity->IsLocalPlayer();
|
||
|
}
|
||
|
|
||
|
if( flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) )
|
||
|
{
|
||
|
weaponDebugf3("DF_MeleeDamage");
|
||
|
|
||
|
// Special handling of melee weapon impacts.
|
||
|
//This is handled in audPedAudioEntity::PlayMeleeCombatHit g_CollisionAudioEntity.ReportMeleeCollision(pResult, fDamageDone, pFiringEntity);
|
||
|
|
||
|
if (playerShot)
|
||
|
{
|
||
|
CPed* pPed = static_cast<CPed*>(pFiringEntity);
|
||
|
CMiniMap::CreateSonarBlipAndReportStealthNoise(pPed, pPed->GetTransform().GetPosition(), CMiniMap::sm_Tunables.Sonar.fSoundRange_ObjectCollision, HUD_COLOUR_BLUEDARK);
|
||
|
}
|
||
|
|
||
|
if (pPedFiringEntity && pPedFiringEntity->IsNetworkClone())
|
||
|
{
|
||
|
weaponDebugf3("CLONE --> invoke ReportMeleeCollision");
|
||
|
g_CollisionAudioEntity.ReportMeleeCollision(pResult, fDamageDone, pPedFiringEntity);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bool bulletHitPlayer = false;
|
||
|
CEntity* pHitEntity = CPhysics::GetEntityFromInst(const_cast<phInst *>(pResult->GetHitInst()));
|
||
|
|
||
|
if(pHitEntity && pHitEntity->GetType() == ENTITY_TYPE_PED)
|
||
|
{
|
||
|
bulletHitPlayer = (static_cast<CPed*>(pHitEntity))->IsLocalPlayer();
|
||
|
}
|
||
|
if(bulletHitPlayer || !SUPERCONDUCTOR.GetGunFightConductor().GetFakeScenesConductor().IsFakingBulletImpacts()
|
||
|
|| (SUPERCONDUCTOR.GetGunFightConductor().GetFakeScenesConductor().IsFakingBulletImpacts() && playerShot))
|
||
|
{
|
||
|
bool isAutomatic = false;
|
||
|
bool isShotgun = false;
|
||
|
f32 timeBetweenShots = 0.f;
|
||
|
const CWeaponInfo* weaponInfo = pWeapon->GetWeaponInfo();
|
||
|
if (weaponInfo)
|
||
|
{
|
||
|
eWeaponEffectGroup effectGroup = weaponInfo->GetEffectGroup();
|
||
|
if (effectGroup == WEAPON_EFFECT_GROUP_SMG || effectGroup == WEAPON_EFFECT_GROUP_RIFLE_ASSAULT)
|
||
|
{
|
||
|
isAutomatic = true;
|
||
|
}
|
||
|
else if (effectGroup == WEAPON_EFFECT_GROUP_SHOTGUN)
|
||
|
{
|
||
|
isShotgun = true;
|
||
|
}
|
||
|
timeBetweenShots = weaponInfo->GetTimeBetweenShots();
|
||
|
}
|
||
|
|
||
|
CAmmoInfo::SpecialType ammoType = CAmmoInfo::None;
|
||
|
|
||
|
if(weaponInfo && pPedFiringEntity)
|
||
|
{
|
||
|
const CAmmoInfo* ammoInfo = weaponInfo->GetAmmoInfo(pPedFiringEntity);
|
||
|
|
||
|
if(ammoInfo)
|
||
|
{
|
||
|
ammoType = ammoInfo->GetAmmoSpecialType();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
g_CollisionAudioEntity.ReportBulletHit(pWeapon->GetWeaponInfo()->GetAudioHash(), pResult, vWeaponPos, pWeapon->GetAudioComponent().GetWeaponSettings(pFiringEntity), ammoType, playerShot, bulletHitPlayer, isAutomatic, isShotgun, timeBetweenShots);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void CWeaponDamage::DoWeaponFiredByPlayer(CWeapon* pWeapon, CEntity* pFiringEntity, const Vector3& UNUSED_PARAM(vWeaponPos), WorldProbe::CShapeTestHitPoint* pResult, const f32 fDamageDone, const fwFlags32& flags)
|
||
|
{
|
||
|
CPed* pPlayerPed = NULL;
|
||
|
if(pFiringEntity && pFiringEntity->GetIsTypePed() && static_cast<CPed*>(pFiringEntity)->IsPlayer())
|
||
|
{
|
||
|
pPlayerPed = static_cast<CPed*>(pFiringEntity);
|
||
|
}
|
||
|
weaponFatalAssertf(pPlayerPed, "pPlayerPed is NULL");
|
||
|
|
||
|
const CWeaponInfo* wi = pWeapon->GetWeaponInfo();
|
||
|
Assert(wi);
|
||
|
|
||
|
// Get the hit entity
|
||
|
CEntity* pHitEntity = CPhysics::GetEntityFromInst(pResult->GetHitInst());
|
||
|
if(pHitEntity)
|
||
|
{
|
||
|
// Cache if we've hit a ped
|
||
|
if(pHitEntity->GetIsTypePed())
|
||
|
{
|
||
|
CPed* pHitPed = static_cast<CPed*>(pHitEntity);
|
||
|
|
||
|
if(wi->GetIsGun())
|
||
|
{
|
||
|
CCrime::ReportCrime(CRIME_POSSESSION_GUN, pHitEntity, pPlayerPed);
|
||
|
|
||
|
if(!pHitPed->IsDead())
|
||
|
{
|
||
|
const WeaponSettings *settings = pWeapon->GetAudioComponent().GetWeaponSettings();
|
||
|
if(settings)
|
||
|
{
|
||
|
bool isNonlethal = AUD_GET_TRISTATE_VALUE(settings->Flags, FLAG_ID_WEAPONSETTINGS_ISNONLETHAL)== AUD_TRISTATE_TRUE;
|
||
|
if(pHitPed->IsLawEnforcementPed())
|
||
|
{
|
||
|
CCrime::ReportCrime(isNonlethal ? CRIME_SHOOT_NONLETHAL_COP : CRIME_SHOOT_COP, pHitEntity, pPlayerPed);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
eCrimeType crimeToReport = isNonlethal ? CRIME_SHOOT_NONLETHAL_PED : (pWeapon->GetIsSilenced() ? CRIME_SHOOT_PED_SUPPRESSED : CRIME_SHOOT_PED);
|
||
|
CCrime::ReportCrime(crimeToReport, pHitEntity, pPlayerPed);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If the damage inflictor is the player then increase the havoc score.
|
||
|
if(fDamageDone > 0.0f && pHitPed)
|
||
|
{
|
||
|
pPlayerPed->GetPlayerInfo()->HavocCaused += HAVOC_PEDDAMAGED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// grab these once
|
||
|
bool bIsPed = pHitEntity->GetIsTypePed();
|
||
|
bool bIsVehicle = pHitEntity->GetIsTypeVehicle();
|
||
|
bool bIsObject = pHitEntity->GetIsTypeObject();
|
||
|
bool bTrackStats = bIsPed || bIsVehicle || bIsObject;
|
||
|
|
||
|
if(bIsPed && NetworkInterface::IsGameInProgress() && static_cast<CPed*>(pHitEntity)->m_nPhysicalFlags.bNotDamagedByAnything)
|
||
|
{
|
||
|
//Turns off stats in network games for entities
|
||
|
//such as hairdressers in script scenes. B*1942049 Exploit
|
||
|
bTrackStats = false;
|
||
|
|
||
|
//And we need to decrease the shots fired by this weapon.
|
||
|
if (pPlayerPed && pPlayerPed->IsLocalPlayer())
|
||
|
{
|
||
|
CStatsMgr::PlayerFiredWeaponToInvenciblePed(pWeapon);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Deal with stats
|
||
|
if(CStatsUtils::CanUpdateStats() && pHitEntity != pPlayerPed && bTrackStats && pPlayerPed->IsLocalPlayer())
|
||
|
{
|
||
|
// track target validity, assume valid
|
||
|
bool bIsValidTargetForStats = true;
|
||
|
|
||
|
if(bIsPed)
|
||
|
{
|
||
|
CPed* pPed = static_cast<CPed*>(pHitEntity);
|
||
|
if(pPed->IsDead())
|
||
|
{
|
||
|
// ped is dead, should not count for stats
|
||
|
bIsValidTargetForStats = false;
|
||
|
}
|
||
|
}
|
||
|
else if(bIsVehicle)
|
||
|
{
|
||
|
CVehicle* pVehicle = static_cast<CVehicle*>(pHitEntity);
|
||
|
if(pVehicle->GetStatus() == STATUS_WRECKED)
|
||
|
{
|
||
|
// vehicle is wrecked, should not count for stats
|
||
|
bIsValidTargetForStats = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// only do stats if we have a valid target
|
||
|
if(bIsValidTargetForStats)
|
||
|
{
|
||
|
bool updateWeaponDamageStats = (0.0f < fDamageDone);
|
||
|
|
||
|
//Check whether we should accept this damage event based on the friendly fire settings
|
||
|
if (NetworkInterface::IsGameInProgress())
|
||
|
{
|
||
|
updateWeaponDamageStats = false;
|
||
|
|
||
|
CPed* pedHit = NULL;
|
||
|
CVehicle* vehicle = NULL;
|
||
|
|
||
|
if(bIsPed)
|
||
|
{
|
||
|
pedHit = static_cast<CPed*>(pHitEntity);
|
||
|
}
|
||
|
else if(bIsVehicle)
|
||
|
{
|
||
|
vehicle = static_cast<CVehicle*>(pHitEntity);
|
||
|
}
|
||
|
else if(bIsObject)
|
||
|
{
|
||
|
CObject* obj = static_cast<CObject*>(pHitEntity);
|
||
|
if (obj->GetIsAttached())
|
||
|
{
|
||
|
CPhysical* attachParent = static_cast<CObject*>(obj->GetAttachParent());
|
||
|
if(attachParent)
|
||
|
{
|
||
|
if(attachParent->GetIsTypePed())
|
||
|
{
|
||
|
pedHit = static_cast<CPed*>(attachParent);
|
||
|
}
|
||
|
else if(attachParent->GetIsTypeVehicle())
|
||
|
{
|
||
|
vehicle = static_cast<CVehicle*>(attachParent);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (pedHit)
|
||
|
{
|
||
|
updateWeaponDamageStats = (0.0f < fDamageDone || (pedHit->IsNetworkClone() && NetworkInterface::IsFriendlyFireAllowed(pedHit, pPlayerPed)));
|
||
|
}
|
||
|
else if (vehicle && vehicle->GetSeatManager())
|
||
|
{
|
||
|
updateWeaponDamageStats = (0.0f < fDamageDone || (vehicle->IsNetworkClone() && NetworkInterface::IsFriendlyFireAllowed(vehicle, pPlayerPed)));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(updateWeaponDamageStats)
|
||
|
{
|
||
|
static u32 uLastFrameOfIncrease = 0;
|
||
|
|
||
|
//Increment Hits for peds and non-empty vehicles
|
||
|
bool incrementStatPedsVehicles = bIsPed;
|
||
|
if (bIsVehicle)
|
||
|
{
|
||
|
incrementStatPedsVehicles = false;
|
||
|
|
||
|
CVehicle* vehc = bIsVehicle ? static_cast<CVehicle*>(pHitEntity) : NULL;
|
||
|
if (vehc && (vehc->GetDriver() || vehc->GetNumberOfPassenger() > 0))
|
||
|
{
|
||
|
int numSeats = vehc->GetLayoutInfo()->GetNumSeats();
|
||
|
for(int s=0; s<numSeats && !incrementStatPedsVehicles; s++)
|
||
|
{
|
||
|
CPed * pPed = vehc->GetPedInSeat(s);
|
||
|
if(pPed && !pPed->IsDead())
|
||
|
{
|
||
|
incrementStatPedsVehicles = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Melee attacks
|
||
|
if (wi->GetIsMelee() || flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ))
|
||
|
{
|
||
|
//We already counted this frame
|
||
|
if (fwTimer::GetSystemFrameCount() != uLastFrameOfIncrease)
|
||
|
{
|
||
|
uLastFrameOfIncrease = fwTimer::GetSystemFrameCount();
|
||
|
|
||
|
//Count UNARMED_PED_HITS in multiplayer.
|
||
|
if (bIsPed && wi->GetHash() == WEAPONTYPE_UNARMED)
|
||
|
{
|
||
|
if(StatsInterface::IsKeyValid(STAT_UNARMED_PED_HITS.GetStatId()))
|
||
|
{
|
||
|
StatsInterface::IncrementStat(STAT_UNARMED_PED_HITS.GetStatId(), 1.0f, STATUPDATEFLAG_ASSERTONLINESTATS);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (incrementStatPedsVehicles)
|
||
|
{
|
||
|
StatId statWeaponHit = StatsInterface::GetLocalPlayerWeaponInfoStatId(StatsInterface::WEAPON_STAT_HITS, wi, pPlayerPed);
|
||
|
if(StatsInterface::IsKeyValid(statWeaponHit))
|
||
|
{
|
||
|
StatsInterface::IncrementStat(statWeaponHit, 1.0f, STATUPDATEFLAG_ASSERTONLINESTATS);
|
||
|
}
|
||
|
#if __ASSERT
|
||
|
else if(pPlayerPed->IsArchetypeSet() && StatsInterface::GetStatsPlayerModelValid())
|
||
|
{
|
||
|
CStatsMgr::MissingWeaponStatName(STATTYPE_WEAPON_HITS, statWeaponHit, wi->GetHash(), wi->GetDamageType(), wi->GetName());
|
||
|
}
|
||
|
#endif // __ASSERT
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
//Gun attacks
|
||
|
// - If the weapon fires multiple bullets in a spread, only update the stat once
|
||
|
// - If a bullet hits multiple things in the same frame, only update stat once (ie window+sit of a car, or car+driver)
|
||
|
else if (fwTimer::GetSystemFrameCount() != uLastFrameOfIncrease)
|
||
|
{
|
||
|
uLastFrameOfIncrease = fwTimer::GetSystemFrameCount();
|
||
|
|
||
|
if (incrementStatPedsVehicles)
|
||
|
{
|
||
|
StatId statWeaponFired = StatsInterface::GetLocalPlayerWeaponInfoStatId(StatsInterface::WEAPON_STAT_SHOTS, wi, pPlayerPed);
|
||
|
StatId statWeaponHit = StatsInterface::GetLocalPlayerWeaponInfoStatId(StatsInterface::WEAPON_STAT_HITS, wi, pPlayerPed);
|
||
|
if(StatsInterface::IsKeyValid(statWeaponFired) && StatsInterface::IsKeyValid(statWeaponHit))
|
||
|
{
|
||
|
if(StatsInterface::GetIntStat(statWeaponHit) < StatsInterface::GetIntStat(statWeaponFired))
|
||
|
{
|
||
|
StatsInterface::IncrementStat(statWeaponHit, 1.0f, STATUPDATEFLAG_ASSERTONLINESTATS); //Increment this bullet hit in the stats
|
||
|
|
||
|
//Hit a vehicle
|
||
|
CVehicle* vehc = bIsVehicle ? static_cast<CVehicle*>(pHitEntity) : NULL;
|
||
|
if (vehc)
|
||
|
{
|
||
|
if (VEHICLE_TYPE_CAR == vehc->GetVehicleType())
|
||
|
{
|
||
|
StatId hitCar = StatsInterface::GetLocalPlayerWeaponInfoStatId(StatsInterface::WEAPON_STAT_HITS_CAR, wi, pPlayerPed);
|
||
|
if (StatsInterface::IsKeyValid(hitCar))
|
||
|
{
|
||
|
StatsInterface::IncrementStat(hitCar, 1.0f, STATUPDATEFLAG_ASSERTONLINESTATS);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
#if __ASSERT
|
||
|
else if(pPlayerPed->IsArchetypeSet() && StatsInterface::GetStatsPlayerModelValid())
|
||
|
{
|
||
|
CStatsMgr::MissingWeaponStatName(STATTYPE_WEAPON_HITS, statWeaponFired, wi->GetHash(), wi->GetDamageType(), wi->GetName());
|
||
|
}
|
||
|
#endif // __ASSERT
|
||
|
}
|
||
|
|
||
|
//Do not count for vehicle weapons.
|
||
|
if (!wi->GetIsVehicleWeapon())
|
||
|
{
|
||
|
StatId statPedFired = STAT_SHOTS.GetStatId();
|
||
|
StatId statPedHit = STAT_HITS.GetStatId();
|
||
|
|
||
|
if(StatsInterface::IsKeyValid(statPedFired) && StatsInterface::IsKeyValid(statPedHit))
|
||
|
{
|
||
|
if(StatsInterface::GetIntStat(statPedHit) < StatsInterface::GetIntStat(statPedFired))
|
||
|
{
|
||
|
StatsInterface::IncrementStat(statPedHit, 1.0f, STATUPDATEFLAG_ASSERTONLINESTATS); //Increment this bullet hit in the stats
|
||
|
}
|
||
|
|
||
|
//Hits during a mission
|
||
|
if (incrementStatPedsVehicles && CTheScripts::GetPlayerIsOnAMission())
|
||
|
{
|
||
|
StatId statPedHitMission = STAT_HITS_MISSION.GetStatId();
|
||
|
if (StatsInterface::IsKeyValid(statPedHitMission))
|
||
|
{
|
||
|
if(StatsInterface::GetIntStat(statPedHitMission) < StatsInterface::GetIntStat(statPedFired))
|
||
|
{
|
||
|
StatsInterface::IncrementStat(statPedHitMission, 1.0f); //Increment this bullet hit in the stats
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (incrementStatPedsVehicles && StatsInterface::IsKeyValid(STAT_HITS_PEDS_VEHICLES.GetHash()))
|
||
|
{
|
||
|
StatsInterface::IncrementStat(STAT_HITS_PEDS_VEHICLES.GetStatId(), 1.0f, STATUPDATEFLAG_ASSERTONLINESTATS);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Is inside Vehicle
|
||
|
CVehicle* pVehicle = pPlayerPed->GetVehiclePedInside();
|
||
|
if(pVehicle)
|
||
|
{
|
||
|
const bool playerIsDriver = (pPlayerPed == pVehicle->GetDriver());
|
||
|
|
||
|
if (playerIsDriver)
|
||
|
{
|
||
|
StatId statPedDriveByHit = STAT_DB_HITS.GetStatId();
|
||
|
if(StatsInterface::GetIntStat(statPedDriveByHit) < StatsInterface::GetIntStat(STAT_DB_SHOTS.GetStatId()))
|
||
|
{
|
||
|
StatsInterface::IncrementStat(statPedDriveByHit, 1.0f, STATUPDATEFLAG_ASSERTONLINESTATS); // Increment this bullet hit in the stats
|
||
|
}
|
||
|
|
||
|
if (incrementStatPedsVehicles && StatsInterface::IsKeyValid(STAT_DB_HITS_PEDS_VEHICLES))
|
||
|
{
|
||
|
StatsInterface::IncrementStat(STAT_DB_HITS_PEDS_VEHICLES.GetStatId(), 1.0f, STATUPDATEFLAG_ASSERTONLINESTATS);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
StatId statPedDriveByFired = STAT_PASS_DB_SHOTS.GetStatId();
|
||
|
StatId statPedDriveByHit = STAT_PASS_DB_HITS.GetStatId();
|
||
|
if(StatsInterface::GetIntStat(statPedDriveByHit) < StatsInterface::GetIntStat(statPedDriveByFired))
|
||
|
{
|
||
|
StatsInterface::IncrementStat(statPedDriveByHit, 1.0f, STATUPDATEFLAG_ASSERTONLINESTATS); // Increment this bullet hit in the stats
|
||
|
}
|
||
|
|
||
|
if (incrementStatPedsVehicles && StatsInterface::IsKeyValid(STAT_PASS_DB_HITS_PEDS_VEHICLES))
|
||
|
{
|
||
|
StatsInterface::IncrementStat(STAT_PASS_DB_HITS_PEDS_VEHICLES.GetStatId(), 1.0f, STATUPDATEFLAG_ASSERTONLINESTATS);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
void CWeaponDamage::SetLastSignificantShotBone(CWeapon* pWeapon, CPed* pHitPed, WorldProbe::CShapeTestHitPoint* pResult)
|
||
|
{
|
||
|
// Set the last significant shot bone
|
||
|
if(pResult)
|
||
|
{
|
||
|
s32 iHitComponent = pResult->GetHitComponent();
|
||
|
s32 iHitBoneTag = pHitPed->GetBoneTagFromRagdollComponent(iHitComponent);
|
||
|
if(iHitBoneTag == BONETAG_NECK || iHitBoneTag == BONETAG_HEAD || iHitBoneTag == BONETAG_NECKROLL)
|
||
|
{
|
||
|
// Head has been hit - all weapons are significant apart from punch/kick
|
||
|
if(pWeapon->GetWeaponInfo()->GetEffectGroup() >= WEAPON_EFFECT_GROUP_MELEE_WOOD)
|
||
|
{
|
||
|
pHitPed->SetLastSignificantShotBoneTag(iHitBoneTag);
|
||
|
}
|
||
|
}
|
||
|
else if(iHitBoneTag == BONETAG_ROOT || iHitBoneTag == BONETAG_PELVIS || iHitBoneTag == BONETAG_SPINE0 || iHitBoneTag == BONETAG_SPINE1 || iHitBoneTag == BONETAG_SPINE2 || iHitBoneTag == BONETAG_SPINE3 || iHitBoneTag == BONETAG_R_CLAVICLE || iHitBoneTag == BONETAG_L_CLAVICLE)
|
||
|
{
|
||
|
// Torso has been hit - gun weapons are significant
|
||
|
if(pWeapon->GetWeaponInfo()->GetEffectGroup() >= WEAPON_EFFECT_GROUP_PISTOL_SMALL)
|
||
|
{
|
||
|
pHitPed->SetLastSignificantShotBoneTag(iHitBoneTag);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
CTask* CWeaponDamage::GenerateRagdollTask(CEntity* pFiringEntity, CPed* pHitPed, const u32 uWeaponHash, const f32 fWeaponDamage, const fwFlags32& flags,
|
||
|
const bool bWasKilledOrInjured, const Vector3& vStart, WorldProbe::CShapeTestHitPoint* pResult,
|
||
|
const Vector3& vRagdollImpulseDir, const f32 fRagdollImpulseMag, bool &bTaskAppliesImpulse, bool &bUpdatedPreviousEvent)
|
||
|
{
|
||
|
// Get the weapon info
|
||
|
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(uWeaponHash);
|
||
|
if(!pWeaponInfo)
|
||
|
{
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (!pHitPed)
|
||
|
{
|
||
|
weaponAssertf(pHitPed, "CWeaponDamage::GenerateRagdollTask - pHitPed is NULL");
|
||
|
return NULL;
|
||
|
}
|
||
|
weaponAssertf(pHitPed->GetPedIntelligence(), "CWeaponDamage::GenerateRagdollTask - pHitPed->GetPedIntelligence() is NULL");
|
||
|
|
||
|
#if __ASSERT
|
||
|
if(pResult)
|
||
|
{
|
||
|
weaponAssertf(pResult->GetHitNormal().IsNonZero(), "Invalid intersection normal");
|
||
|
}
|
||
|
#endif // __ASSERT
|
||
|
|
||
|
// First, assume that the task isn't assuming responsibility for applying the impulse
|
||
|
bTaskAppliesImpulse = false;
|
||
|
|
||
|
bool bNeedRagdollTask = false;
|
||
|
if(!pHitPed->GetUsingRagdoll())
|
||
|
{
|
||
|
bNeedRagdollTask = true;
|
||
|
|
||
|
f32 fKnockOffProp = fwRandom::GetRandomNumberInRange(0.0, 1.0f);
|
||
|
if(fWeaponDamage > 30.0f)
|
||
|
{
|
||
|
fKnockOffProp *= 1.3f;
|
||
|
}
|
||
|
|
||
|
if(NetworkInterface::IsGameInProgress())
|
||
|
{
|
||
|
fKnockOffProp = 0.0f;
|
||
|
}
|
||
|
|
||
|
if(fKnockOffProp > 0.9f)
|
||
|
{
|
||
|
CPedPropsMgr::KnockOffProps(pHitPed, true, true, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Need a task if killed and not running an appropriate ragdoll reaction
|
||
|
CTask* pTaskSimplest = pHitPed->GetPedIntelligence()->GetTaskActiveSimplest();
|
||
|
|
||
|
if (pTaskSimplest && pTaskSimplest->IsNMBehaviourTask())
|
||
|
{
|
||
|
CTaskNMBehaviour* pRunningNmTask = smart_cast<CTaskNMBehaviour*>(pTaskSimplest);
|
||
|
bNeedRagdollTask = pRunningNmTask->ShouldAbortForWeaponDamage(pFiringEntity,pWeaponInfo, fWeaponDamage, flags, bWasKilledOrInjured, vStart, pResult, vRagdollImpulseDir, fRagdollImpulseMag);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bNeedRagdollTask = true;
|
||
|
}
|
||
|
|
||
|
|
||
|
CTask* pTaskResult = NULL;
|
||
|
if(bNeedRagdollTask)
|
||
|
{
|
||
|
CTaskNMBehaviour* pTaskNM = NULL;
|
||
|
|
||
|
if(pWeaponInfo->GetDamageType() == DAMAGE_TYPE_FIRE)
|
||
|
{
|
||
|
if(bWasKilledOrInjured)
|
||
|
{
|
||
|
pTaskNM = rage_new CTaskNMOnFire(1000, 30000, 0.3f);
|
||
|
|
||
|
if(CTaskNMBehaviour::ms_bUseParameterSets && !pHitPed->CheckAgilityFlags(AF_RAGDOLL_ON_FIRE_STRONG))
|
||
|
{
|
||
|
static_cast<CTaskNMOnFire*>(pTaskNM)->SetType(CTaskNMOnFire::ONFIRE_WEAK);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pTaskResult = rage_new CTaskComplexOnFire(uWeaponHash);
|
||
|
}
|
||
|
}
|
||
|
else if(pWeaponInfo->GetDamageType() == DAMAGE_TYPE_EXPLOSIVE)
|
||
|
{
|
||
|
Vector3 vExplosionPos = vStart;
|
||
|
|
||
|
// Run explosion task.
|
||
|
pTaskNM = rage_new CTaskNMExplosion(1000, 30000, vExplosionPos);
|
||
|
|
||
|
if (pHitPed && pHitPed->GetFacialData())
|
||
|
{
|
||
|
if (pHitPed->IsLocalPlayer())
|
||
|
{
|
||
|
pHitPed->GetFacialData()->PlayDyingFacialAnim(pHitPed);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pHitPed->GetFacialData()->PlayPainFacialAnim(pHitPed);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (pHitPed->GetPedResetFlag(CPED_RESET_FLAG_WasHitByVehicleMelee))
|
||
|
{
|
||
|
u32 minTime = (u32)(pHitPed->IsPlayer() ? CTaskNMShot::sm_Tunables.m_MinimumShotReactionTimePlayerMS : CTaskNMShot::sm_Tunables.m_MinimumShotReactionTimeAIMS);
|
||
|
pTaskNM = rage_new CTaskNMShot( pHitPed,
|
||
|
minTime,
|
||
|
10000,
|
||
|
pFiringEntity,
|
||
|
uWeaponHash,
|
||
|
pResult->GetHitComponent(),
|
||
|
pResult->GetHitPosition(),
|
||
|
fRagdollImpulseMag * vRagdollImpulseDir,
|
||
|
pResult->GetHitNormal() );
|
||
|
}
|
||
|
else if( flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) &&
|
||
|
!flags.IsFlagSet( CPedDamageCalculator::DF_SelfDamage ) )
|
||
|
{
|
||
|
Vector3 vHitNormal;
|
||
|
Vector3 vHitPos;
|
||
|
s32 nHitComponent;
|
||
|
if(pResult)
|
||
|
{
|
||
|
vHitNormal.Negate(pResult->GetHitNormal());
|
||
|
vHitPos = pResult->GetHitPosition();
|
||
|
nHitComponent = (s32)pResult->GetHitComponent();
|
||
|
taskDisplayf("melee hit: position (x:%.3f, y:%.3f, z:%.3f) Normal (x:%.3f, y:%.3f, z:%.3f) ragdollImpulseDir (x:%.3f, y:%.3f, z:%.3f)"
|
||
|
, vHitPos.x, vHitPos.y, vHitPos.z
|
||
|
, vHitNormal.x, vHitNormal.y, vHitNormal.z
|
||
|
, vRagdollImpulseDir.x, vRagdollImpulseDir.y, vRagdollImpulseDir.z);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
vHitNormal = -VEC3V_TO_VECTOR3(pHitPed->GetTransform().GetForward());
|
||
|
vHitPos = VEC3V_TO_VECTOR3(pHitPed->GetTransform().GetPosition());
|
||
|
nHitComponent = -1;
|
||
|
}
|
||
|
|
||
|
Vector3 vMeleeRagdollImpulseDir = vRagdollImpulseDir;
|
||
|
|
||
|
// Apply script-set force modifier to impulse direction
|
||
|
if (pFiringEntity && pFiringEntity->GetIsTypePed())
|
||
|
{
|
||
|
const CPed* pFiringPed = static_cast<CPed*>(pFiringEntity);
|
||
|
if (pFiringPed->IsPlayer() && uWeaponHash != pFiringPed->GetDefaultUnarmedWeaponHash())
|
||
|
{
|
||
|
vMeleeRagdollImpulseDir *= pFiringPed->GetPlayerInfo()->GetPlayerMeleeWeaponForceModifier();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pTaskNM = rage_new CTaskNMFlinch(1000, 10000, VEC3V_TO_VECTOR3(pFiringEntity ? pFiringEntity->GetTransform().GetPosition() : Vec3V(V_ZERO)), pFiringEntity, CTaskNMFlinch::FLINCHTYPE_MELEE,
|
||
|
uWeaponHash, nHitComponent, false, pHitPed, &vHitPos, &vHitNormal, &vMeleeRagdollImpulseDir);
|
||
|
Assert(pTaskNM);
|
||
|
|
||
|
if (CTaskNMBehaviour::ms_bUseParameterSets && !pHitPed->CheckAgilityFlags(AF_DONT_FLINCH_ON_MELEE))
|
||
|
{
|
||
|
static_cast<CTaskNMFlinch*>(pTaskNM)->SetType(CTaskNMFlinch::FLINCHTYPE_MELEE_PASSIVE);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
static_cast<CTaskNMFlinch*>(pTaskNM)->SetType(CTaskNMFlinch::FLINCHTYPE_MELEE);
|
||
|
}
|
||
|
|
||
|
bTaskAppliesImpulse = true;
|
||
|
}
|
||
|
else if( uWeaponHash == WEAPONTYPE_FALL || (flags.IsFlagSet( CPedDamageCalculator::DF_MeleeDamage ) && ( bWasKilledOrInjured || flags.IsFlagSet( CPedDamageCalculator::DF_SelfDamage ) )))
|
||
|
{
|
||
|
static float stiffness = 0.0f;
|
||
|
static float damping = 0.0f;
|
||
|
pTaskNM = rage_new CTaskNMRelax(2000, 60000, stiffness, damping);
|
||
|
}
|
||
|
else if((uWeaponHash == WEAPONTYPE_SMOKEGRENADE || uWeaponHash == WEAPONTYPE_DLC_BOMB_GAS || uWeaponHash == WEAPONTYPE_DLC_BZGAS_MK2) && pHitPed->HasHurtStarted())
|
||
|
{
|
||
|
pTaskNM = rage_new CTaskNMHighFall(1000, NULL, CTaskNMHighFall::HIGHFALL_SLOPE_SLIDE);
|
||
|
}
|
||
|
else if(uWeaponHash == WEAPONTYPE_ROTORS)
|
||
|
{
|
||
|
Vector3 vPedVelocity(pHitPed->GetVelocity());
|
||
|
if (MagSquared(pHitPed->GetGroundVelocityIntegrated()).Getf() > vPedVelocity.Mag2())
|
||
|
{
|
||
|
vPedVelocity = VEC3V_TO_VECTOR3(pHitPed->GetGroundVelocityIntegrated());
|
||
|
}
|
||
|
pTaskNM = rage_new CTaskNMBrace(1000, 10000, pFiringEntity, CTaskNMBrace::BRACE_DEFAULT, vPedVelocity);
|
||
|
}
|
||
|
else if (uWeaponHash == WEAPONTYPE_DLC_SNOWBALL)
|
||
|
{
|
||
|
eAnimBoneTag iHitBoneTag = pHitPed->GetBoneTagFromRagdollComponent((int)pResult->GetHitComponent());
|
||
|
float impulseMag = pWeaponInfo->GetForceHitPed(iHitBoneTag, true, 0.0f);
|
||
|
|
||
|
u32 minTime = (u32)(pHitPed->IsPlayer() ? CTaskNMShot::sm_Tunables.m_MinimumShotReactionTimePlayerMS : CTaskNMShot::sm_Tunables.m_MinimumShotReactionTimeAIMS);
|
||
|
pTaskNM = rage_new CTaskNMShot(pHitPed, minTime, 10000, pFiringEntity, uWeaponHash, pResult->GetHitComponent(), pResult->GetHitPosition(), impulseMag * vRagdollImpulseDir, pResult->GetHitNormal());
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(pResult)
|
||
|
{
|
||
|
bool bShotByPlayer = false;
|
||
|
if(pFiringEntity && pFiringEntity->GetIsTypePed() && ((CPed*)pFiringEntity)->IsPlayer())
|
||
|
{
|
||
|
bShotByPlayer = true;
|
||
|
}
|
||
|
|
||
|
if(pTaskNM == NULL)
|
||
|
{
|
||
|
// Check if a shot task has already been created this frame
|
||
|
CTaskNMShot *shotTask = NULL;
|
||
|
if (pHitPed->GetPedIntelligence())
|
||
|
{
|
||
|
if (CEventDamage *damageEvent = static_cast<CEventDamage*>(pHitPed->GetPedIntelligence()->GetEventOfType(EVENT_DAMAGE)))
|
||
|
{
|
||
|
if (damageEvent->GetPhysicalResponseTask() && damageEvent->GetPhysicalResponseTask()->GetTaskType() == CTaskTypes::TASK_NM_CONTROL)
|
||
|
{
|
||
|
const CTaskNMControl *controlTask = smart_cast<const CTaskNMControl*>(damageEvent->GetPhysicalResponseTask());
|
||
|
if (controlTask->GetForcedSubTask() && controlTask->GetForcedSubTask()->GetTaskType() == CTaskTypes::TASK_NM_SHOT)
|
||
|
{
|
||
|
shotTask = (CTaskNMShot*) controlTask->GetForcedSubTask();
|
||
|
shotTask->UpdateShot(pHitPed, pFiringEntity, uWeaponHash, pResult->GetHitComponent(), pResult->GetHitPosition(), fRagdollImpulseMag * vRagdollImpulseDir, pResult->GetHitNormal());
|
||
|
|
||
|
// Set the task result so that the logic below doesn't choke
|
||
|
pTaskResult = const_cast<CTaskNMControl*>(controlTask);
|
||
|
bUpdatedPreviousEvent = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (pHitPed->IsNetworkClone())
|
||
|
{
|
||
|
CTask* pTask = pHitPed->GetPedIntelligence()->GetActiveCloneTask();
|
||
|
if (pTask && pTask->GetTaskType() == CTaskTypes::TASK_NM_CONTROL)
|
||
|
{
|
||
|
const CTaskNMControl *controlTask = smart_cast<const CTaskNMControl*>(pTask);
|
||
|
if (controlTask && controlTask->GetForcedSubTask() && controlTask->GetForcedSubTask()->GetTaskType() == CTaskTypes::TASK_NM_SHOT)
|
||
|
{
|
||
|
shotTask = (CTaskNMShot*) controlTask->GetForcedSubTask();
|
||
|
if (shotTask)
|
||
|
{
|
||
|
shotTask->UpdateShot(pHitPed, pFiringEntity, uWeaponHash, pResult->GetHitComponent(), pResult->GetHitPosition(), fRagdollImpulseMag * vRagdollImpulseDir, pResult->GetHitNormal());
|
||
|
|
||
|
// Set the task result so that the logic below doesn't choke
|
||
|
pTaskResult = const_cast<CTaskNMControl*>(controlTask);
|
||
|
bUpdatedPreviousEvent = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!shotTask)
|
||
|
{
|
||
|
u32 minTime = (u32)(pHitPed->IsPlayer() ? CTaskNMShot::sm_Tunables.m_MinimumShotReactionTimePlayerMS : CTaskNMShot::sm_Tunables.m_MinimumShotReactionTimeAIMS);
|
||
|
pTaskNM = rage_new CTaskNMShot(pHitPed, minTime, 10000, pFiringEntity, uWeaponHash, pResult->GetHitComponent(), pResult->GetHitPosition(), fRagdollImpulseMag * vRagdollImpulseDir, pResult->GetHitNormal());
|
||
|
}
|
||
|
|
||
|
// NM will take care of applying the bullet impulse
|
||
|
if (NM_APPLIES_SHOT_IMPULSE && (pTaskNM || shotTask) && pTaskSimplest->GetTaskType() != CTaskTypes::TASK_NM_DANGLE) // When dangling we handle the impulse here
|
||
|
bTaskAppliesImpulse = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If injured then we're going to send a die event which will create its own complex die task. We still
|
||
|
// need to wrap the NM behaviour task with a control task though.
|
||
|
if(pTaskNM)
|
||
|
{
|
||
|
u32 uNmControlFlags = CTaskNMControl::ALL_FLAGS_CLEAR;
|
||
|
if(!bWasKilledOrInjured)
|
||
|
{
|
||
|
uNmControlFlags |= CTaskNMControl::DO_BLEND_FROM_NM;
|
||
|
}
|
||
|
|
||
|
pTaskResult = rage_new CTaskNMControl(pTaskNM->GetMinTime(), pTaskNM->GetMaxTime(), pTaskNM, uNmControlFlags);
|
||
|
}
|
||
|
|
||
|
weaponAssert(pTaskResult);
|
||
|
}
|
||
|
// If no new task was created but a shot task was already running then assume that it already applied impulse(s)
|
||
|
else if (NM_APPLIES_SHOT_IMPULSE && pTaskSimplest != NULL && pTaskSimplest->GetTaskType() == CTaskTypes::TASK_NM_SHOT)
|
||
|
{
|
||
|
bTaskAppliesImpulse = true;
|
||
|
}
|
||
|
|
||
|
nmEntityDebugf(pHitPed, "CWeaponDamage::GenerateRagdollTask - returning ragdoll task %s", pTaskResult ? pTaskResult->GetName() : "None");
|
||
|
return pTaskResult;
|
||
|
}
|
||
|
|
||
|
bool CWeaponDamage::CanDropWeaponWhenShot(const CWeapon& /*rWeapon*/, const CPed& /*rHitPed*/, const CWeapon& /*rHitWeapon*/)
|
||
|
{
|
||
|
#if 1 // Disable weapons being shot out of hands
|
||
|
return false;
|
||
|
#else
|
||
|
//The weapon doing the damage must not be unarmed.
|
||
|
if(rWeapon.GetWeaponInfo()->GetIsUnarmed())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//Ensure the hit weapon info is valid.
|
||
|
const CWeaponInfo* pHitWeaponInfo = rHitWeapon.GetWeaponInfo();
|
||
|
if(!pHitWeaponInfo)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//Ensure the hit weapon can generate a pickup.
|
||
|
if(pHitWeaponInfo->GetPickupHash() == 0)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
//Check if the hit weapon is a gun.
|
||
|
if(pHitWeaponInfo->GetIsGun())
|
||
|
{
|
||
|
//Check if this is the ped's only gun.
|
||
|
const CPedInventory* pInv = rHitPed.GetInventory();
|
||
|
if(!pInv || (pInv->GetWeaponRepository().GetNumGuns() <= 1))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//Ensure the ped is not in a vehicle.
|
||
|
if(rHitPed.GetIsInVehicle())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
eRagdollTriggerTypes CWeaponDamage::GetRagdollTriggerForDamageType(const eDamageType damageType)
|
||
|
{
|
||
|
switch(damageType)
|
||
|
{
|
||
|
case DAMAGE_TYPE_ELECTRIC: return RAGDOLL_TRIGGER_ELECTRIC;
|
||
|
case DAMAGE_TYPE_BULLET_RUBBER: return RAGDOLL_TRIGGER_RUBBERBULLET;
|
||
|
|
||
|
default:
|
||
|
weaponAssertf(0, "No ragdoll trigger type is defined for damage type %i", (int)damageType);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return RAGDOLL_TRIGGER_ELECTRIC;
|
||
|
}
|
||
|
|
||
|
bool CWeaponDamage::IsEnduranceDamage(CPed* pPed, const CWeapon* pWeapon, const fwFlags32& flags)
|
||
|
{
|
||
|
const CPlayerInfo* pPlayerInfo = pPed->GetPlayerInfo();
|
||
|
if (!pPlayerInfo || pPlayerInfo->GetEnduranceManager().ShouldIgnoreEnduranceDamage())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (flags.IsFlagSet(CPedDamageCalculator::DF_EnduranceDamageOnly))
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (pWeapon->GetWeaponInfo()->GetEnduranceDamage() > 0.0f)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|