// // 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(pFiringEntity) : NULL; if(!pFiringPed && pFiringEntity && pFiringEntity->GetIsTypeVehicle()) { pFiringPed = static_cast(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(it->GetHitInst())->MapRagdollLODComponentCurrentToHigh(it->GetHitComponent()); if(highLODComponent >= 0) { it->SetHitComponent(static_cast(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(it->GetHitInst())->MapRagdollLODComponentHighToCurrent(it->GetHitComponent()); if(currentLODComponent >= 0) { it->SetHitComponent(static_cast(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(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(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(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(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(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 (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(pHitEntity) : NULL; if(!pHitPed) { pFiringPed->SetPedResetFlag( CPED_RESET_FLAG_MeleeStrikeAgainstNonPed, true ); if(pHitEntity && pHitEntity->GetIsTypeObject()) { CObject* pHitObj = static_cast(pHitEntity); CEntity* pHitAttachParent = (CPhysical *) pHitObj->GetAttachParent(); if(pHitAttachParent && pHitAttachParent->GetIsTypePed()) { pHitPed = static_cast(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(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(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(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( 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(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(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( 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(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(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(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(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(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(pTaskNM)->SetType(CTaskNMFlinch::FLINCHTYPE_MELEE_PASSIVE); } else { static_cast(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(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(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(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(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(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(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( 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((tempDamageEvent.GetDamageResponseData().m_bHeadShot ? pWeaponInfo->GetDamageTimeInVehicleHeadShot() : pWeaponInfo->GetDamageTimeInVehicle())*1000.0f); } else { uDamageTime = static_cast(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(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(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(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(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(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(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( 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( pFiringEntity ); static dev_bool s_bCacheRemoteMeleeDamage = true; bool bDoMeleeDamage = false; if (flags.IsFlagSet( CPedDamageCalculator::DF_VehicleMeleeHit) && pFiringEntity->GetIsTypePed()) { CPed* pFiringPed = static_cast( pFiringEntity ); CTaskMountThrowProjectile* pProjTask = static_cast(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(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(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(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(pHitPed->GetNetworkObject())->KeepProximityControl(); } } } // Projectile Impact (Flare Gun) if (pWeapon->GetWeaponInfo()->GetIsProjectile() && !flags.IsFlagSet(CPedDamageCalculator::DF_MeleeDamage)) { const CAmmoProjectileInfo* pAmmoInfo = static_cast(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(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(pHitEntity); bool bFirerInSameVehicleAsHitEntity = false; if (NetworkInterface::IsGameInProgress()) { if(fApplyDamage > FORCE_THRESHOLD_FOR_HIGH_UPDATE) { if(pHitVehicle->IsNetworkClone()) { NetworkInterface::OverrideNetworkBlenderForTime(static_cast(pHitEntity), BLENDER_OVERRIDE_LENGTH); } else { NetworkInterface::ForceHighUpdateLevel(static_cast(pHitEntity)); } } if (pFiringEntity && pFiringEntity->GetIsTypePed()) { CPed* pFiringPed = static_cast(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(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(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(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(pFiringEntity)->IsPlayer()) { bPlayerResponsible = true; //Don't apply force to my cover object CPed* pFiringPlayer = static_cast(pFiringEntity); if (pFiringPlayer->GetIsInCover()) { CCoverPoint* pCoverPoint = pFiringPlayer->GetCoverPoint(); if (pCoverPoint && pCoverPoint->GetEntity() == pHitVehicle && bApplyForce) { bNoForceDueToHitCover = true; bApplyForce = false; } } } else if(pFiringEntity->GetIsDynamic() && static_cast(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(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(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; doorIndexGetNumDoors(); 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(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(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(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(pHitObj)->ProcessWeaponImpact(pFiringEntity)) { weaponDebugf3("CWeaponDamage::DoWeaponImpactObject--(pHitObj->m_nObjectFlags.bIsPickUp && pFiringEntity && static_cast(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(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(pChildAttachment)->GetIsTypePed()) { // If this is a ped that's sitting... const CPed& attachedPed = *static_cast(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(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(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(pHitEntity); if(pHitVehicle->InheritsFromHeli()) { pSearchLight = static_cast(pHitVehicle)->GetSearchLight(); } else if(pHitVehicle->InheritsFromBoat()) { pSearchLight = static_cast(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(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(pHitAttachParent); if(!NetworkUtils::IsNetworkCloneOrMigrating(pHitPed)) { if(!pHitPed->IsAPlayerPed()) { const CPed* pFiringPlayerPed = pFiringEntity && pFiringEntity->GetIsTypePed() && static_cast(pFiringEntity)->IsAPlayerPed() ? static_cast(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(pFiringEntity)->IsAllowedToDamageEntity(pWeapon->GetWeaponInfo(), pNewHitEntity)) || (pFiringEntity->GetIsTypeVehicle() && static_cast(pFiringEntity)->GetDriver() && !static_cast(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(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(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(pFiringEntity); if (pFiringPed && pFiringPed->GetIsInVehicle()) { CVehicle *pVehicle = pFiringPed->GetVehiclePedInside(); CVehicle *pHitVehicle = static_cast(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(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(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(pHitEntity); CPed* pFiringPed = static_cast(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(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(pHitEntity); if (pVehicle && pVehicle->GetNetworkObject()) { CNetObjVehicle* pNetObjVehicle = static_cast(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(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(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(pResult->GetHitInst())); if(pHitEntity && pHitEntity->GetType() == ENTITY_TYPE_PED) { bulletHitPlayer = (static_cast(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(pFiringEntity)->IsPlayer()) { pPlayerPed = static_cast(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(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(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(pHitEntity); if(pPed->IsDead()) { // ped is dead, should not count for stats bIsValidTargetForStats = false; } } else if(bIsVehicle) { CVehicle* pVehicle = static_cast(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(pHitEntity); } else if(bIsVehicle) { vehicle = static_cast(pHitEntity); } else if(bIsObject) { CObject* obj = static_cast(pHitEntity); if (obj->GetIsAttached()) { CPhysical* attachParent = static_cast(obj->GetAttachParent()); if(attachParent) { if(attachParent->GetIsTypePed()) { pedHit = static_cast(attachParent); } else if(attachParent->GetIsTypeVehicle()) { vehicle = static_cast(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(pHitEntity) : NULL; if (vehc && (vehc->GetDriver() || vehc->GetNumberOfPassenger() > 0)) { int numSeats = vehc->GetLayoutInfo()->GetNumSeats(); for(int s=0; sGetPedInSeat(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(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(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(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(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(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(pTaskNM)->SetType(CTaskNMFlinch::FLINCHTYPE_MELEE_PASSIVE); } else { static_cast(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(pHitPed->GetPedIntelligence()->GetEventOfType(EVENT_DAMAGE))) { if (damageEvent->GetPhysicalResponseTask() && damageEvent->GetPhysicalResponseTask()->GetTaskType() == CTaskTypes::TASK_NM_CONTROL) { const CTaskNMControl *controlTask = smart_cast(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(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(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(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; }