// File header #include "Weapons/Projectiles/Projectile.h" // Task header #include "Task/Weapons/TaskProjectile.h" // Rage headers #include "audiosoundtypes/sound.h" #include "fwscene/stores/staticboundsstore.h" #include "fwscene/world/worldlimits.h" // Game headers #include "Audio/WeaponAudioEntity.h" #include "camera/CamInterface.h" #include "cutscene/CutSceneManagerNew.h" #include "Event/Events.h" #include "Event/EventDamage.h" #include "Event/EventGroup.h" #include "event/EventMovement.h" #include "event/EventShocking.h" #include "Event/EventSound.h" #include "Event/EventWeapon.h" #include "event/ShockingEvents.h" #include "Frontend/MiniMap.h" #include "modelinfo/VehicleModelInfoExtensions.h" #include "Network/Arrays/NetworkArrayHandlerTypes.h" #include "Network/Arrays/NetworkArrayMgr.h" #include "Network/Events/NetworkEventTypes.h" #include "Network/Players/NetworkPlayerMgr.h" #include "network/objects/entities/NetObjPlayer.h" #include "Peds/Pedpopulation.h" #include "Peds/Ped.h" #include "peds/PedIntelligence.h" #include "Physics/gtaInst.h" #include "Physics/Physics.h" #include "Renderer/Water.h" #include "scene/EntityIterator.h" #include "Scene/FocusEntity.h" #include "task/Movement/TaskParachute.h" #include "timecycle/TimeCycleConfig.h" #include "vehicles/heli.h" #include "Vehicles/Planes.h" #include "Vehicles/Trailer.h" #include "Vehicles/Metadata/VehicleSeatInfo.h" #include "Vfx/Misc/Coronas.h" #include "Vfx/Particles/PtFxManager.h" #include "Vfx/Systems/VfxProjectile.h" #include "Vfx/Systems/VfxWeapon.h" #include "Vfx/VehicleGlass/VehicleGlassManager.h" #include "Vfx/VfxHelper.h" #include "Task/Movement/TaskParachute.h" #include "Weapons/AirDefence.h" #include "Weapons/Explosion.h" #include "Weapons/Info/WeaponInfoManager.h" #include "Weapons/Projectiles/ProjectileManager.h" #include "Weapons/Projectiles/ProjectileRocket.h" #include "Weapons/Weapon.h" #include "Weapons/WeaponDamage.h" #include "Weapons/WeaponEnums.h" #include "Weapons/WeaponDebug.h" #include "Weapons/WeaponTypes.h" INSTANTIATE_RTTI_CLASS(CProjectile,0x49DC4195) // Macro to disable optimisations if set WEAPON_OPTIMISATIONS() static dev_float PROJECTILE_CEILING_PLANE = WORLDLIMITS_ZMAX - 45.0f; // Using the limits is not enough due to update order static dev_float s_fProjectileFriction = 0.875f; static dev_float s_fProjectileFlareFriction = 1.5f; static dev_float s_fProjectileFirstHitElasticity = 0.4f; static dev_float s_fProjectileFirstHitFriction = 3.0f; static dev_float s_fProjectileFlareFirstHitFriction = 7.0f; static dev_u32 s_uWhizzByEventCheckPeriodMS = 500; static dev_float s_fWhizzByEventRadius = 2.0f; // peds within this distance to the flight path will get whizz by events static dev_float s_fWhizzByEventMinVelocity = 4.0f; // projectile velocity minimum to generate whizz by events const float CProjectile::sm_fMinDistSqForProjectileSonarBlip = 4.0f; static bank_bool s_grenadeAmbientModMPOnly = true; ////////////////////////////////////////////////////////////////////////// // CProjectile ////////////////////////////////////////////////////////////////////////// bool CProjectile::IsEntityInvisibleCardboardBoxAttachedToVehicle(const CEntity& rEnt) { if (rEnt.GetIsTypeObject()) { static const u32 suCardboardBoxHash = ATSTRINGHASH("prop_cs_cardbox_01",0x4da19524); const CObject* pObject = static_cast(&rEnt); if (!pObject->GetIsVisible() && pObject->GetAttachParent() && static_cast(pObject->GetAttachParent())->GetIsTypeVehicle() && pObject->GetArchetype() && pObject->GetArchetype()->GetModelNameHash() == suCardboardBoxHash) { return true; } } return false; } CProjectile::CProjectile(const eEntityOwnedBy ownedBy, u32 uProjectileNameHash, u32 uWeaponFiredFromHash, CEntity* pOwner, float fDamage, eDamageType damageType, eWeaponEffectGroup effectGroup, const CNetFXIdentifier* pNetIdentifier, bool bCreatedFromScript) : CObject(ownedBy, CWeaponInfoManager::GetInfo(uProjectileNameHash)->GetModelIndex()) , m_matPrevious(V_IDENTITY) , m_vLastWhizzByPosition(Vector3::ZeroType) , m_vExplodePos(Vector3::ZeroType) , m_vExplodeNormal(Vector3::ZeroType) , m_vOldSpeed(Vector3::ZeroType) , m_vStickPos(Vector3::ZeroType) , m_vStickNormal(Vector3::ZeroType) , m_networkIdentifier(pNetIdentifier) , m_uHash(uProjectileNameHash) , m_pInfo(static_cast(CWeaponInfoManager::GetInfo(uProjectileNameHash))) , m_uWeaponFiredFromHash(uWeaponFiredFromHash) , m_pOwner(pOwner) , m_taskSequenceId(-1) , m_fAge(0.0f) , m_fLifeTime(0.0f) , m_fLifeTimeAfterImpact(0.0f) , m_fLifeTimeAfterExplosion(0.0f) , m_fTimeStepTimer(0.0f) , m_fExplosionTime(0.0f) , m_fLightIntensityMult(0.0f) , m_fWaterLevel(0.0f) , m_fDamage(fDamage) , m_damageType(damageType) , m_effectGroup(effectGroup) , m_trailEvo(1.0f) , m_FadeTime(0) , m_lightBone(-1) , m_pSound(NULL) , m_pHitEntity(NULL) , m_pOtherInst(NULL) , m_pIgnoreDamageEntity(NULL) , m_iOtherComponent(0) , m_iOtherMaterialId(0) , m_pStickEntity(NULL) , m_iStickComponent(0) , m_iStickMaterialId(0) , m_vStickDeformation(Vector3::ZeroType) , m_pHitPed(NULL) , m_uDestroyTime(0) , m_uExplodeTime(0) , m_vDir(Vector3::ZeroType) , m_pCollisionEntity(NULL) , m_uLastWhizzByEventCheckTimeMS(0) , m_iFlags(0) , m_bThrownFromOutsideOfVehicle(false) , m_bHitPedFriendly(false) , m_bAppliedImpactDamage(false) , m_bCreatedWithoutOwnerFromScript(bCreatedFromScript) , m_bNetworkHasHitPlayer(false) , m_bCanDetonateInstantly(true) , m_bProximityMineTriggered(false) , m_fProximityExplodeTimer(0.0f) , m_bProximityMineTriggeredByVehicle(false) , m_bProximityMineTriggeredByVehicleSpeed(0.0f) , m_fProximityMineStuckTime(0.0f) , m_bProximityMineActive(false) , m_bProximityMineRepeatingDetonation(false) , m_bProximityMineActivationSafeMode(true) , m_bHasTriggeredAttachSound(false) , m_bHideStuckProjectileInVehicle(false) , m_bUseAirDefenceExplosion(false) , m_uTimeAirDefenceExplosionTriggered(0) , m_iLastTimeProcessedHomingAttractor(0) , m_iTimeProjectileWasFired(0) , m_vAirDefenceFireDirection(Vector3::ZeroType) , m_ignoreDamageEntityAttachParent(false) , m_vPositionFiredFrom(Vector3::ZeroType) #if __BANK , m_vLastWhizzByDebugHeadPosition(Vector3::ZeroType) , m_vLastWhizzByDebugTailPosition(Vector3::ZeroType) , m_bWhizzByEventCheckDebugDrawPending(false) #endif // __BANK { SetOwnedBy( ENTITY_OWNEDBY_GAME ); if(NetworkInterface::IsGameInProgress() && !m_networkIdentifier.IsValid()) { // projectiles without an identifier are assigned to our machine during a network game m_networkIdentifier.Set(NetworkInterface::GetLocalPhysicalPlayerIndex(), CNetFXIdentifier::GetNextFXId()); } if(GetInfo()->GetShouldHideDrawable()) { SetIsVisibleForModule( SETISVISIBLE_MODULE_GAMEPLAY, false, true ); } bool bSetDamping = true; if (CBaseModelInfo* pBaseModelInfo = GetBaseModelInfo()) { Assert(pBaseModelInfo->GetModelType() == MI_TYPE_WEAPON); bSetDamping = !static_cast(pBaseModelInfo)->GetDampingTunedViaTunableObjects(); } if (bSetDamping) { phInst* pInst = GetCurrentPhysicsInst(); if (pInst) { phArchetype* pArch = pInst->GetArchetype(); if (pArch) { phArchetypeDamp* pInstArchDamp = dynamic_cast(pArch); if (pInstArchDamp) { float fdamping = GetInfo()->GetDamping(); Vector3 vDamping(fdamping, fdamping, fdamping); // Zero out the non-linearV2 velocity to maintain old damping values now that CWeaponModelInfo::SetPhysics sets all // damping values. // NOTE: This archetype is shared between all projectiles of this type. pInstArchDamp->ActivateDamping(phArchetypeDamp::LINEAR_C, VEC3_ZERO); pInstArchDamp->ActivateDamping(phArchetypeDamp::LINEAR_V, VEC3_ZERO); pInstArchDamp->ActivateDamping(phArchetypeDamp::LINEAR_V2, vDamping); pInstArchDamp->ActivateDamping(phArchetypeDamp::ANGULAR_C, VEC3_ZERO); pInstArchDamp->ActivateDamping(phArchetypeDamp::ANGULAR_V, VEC3_ZERO); pInstArchDamp->ActivateDamping(phArchetypeDamp::ANGULAR_V2, VEC3_ZERO); pInstArchDamp->SetGravityFactor(GetInfo()->GetGravityFactor()); } } } } if( GetBaseModelInfo() ) { m_lightBone = GetInfo()->GetLightBone().GetBoneIndex(GetBaseModelInfo()); } //B*1752582: Fixes bug where smoke grenades which have landed on the peds car fall through when the projectile owner enters it //If owner ped isn't in vehicle, set ThrownFromOutside flag (used in PreComputeImpacts) if(m_pOwner && m_pOwner->GetIsTypePed()) { CPed* pOwnerPed = static_cast( m_pOwner.Get() ) ; if (!pOwnerPed->GetIsInVehicle()) { m_bThrownFromOutsideOfVehicle = true; } if (NetworkInterface::IsGameInProgress()) { s32 taskType = -1; if (pOwnerPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_THROW_PROJECTILE)) { taskType = CTaskTypes::TASK_THROW_PROJECTILE; } else if (pOwnerPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_AIM_AND_THROW_PROJECTILE)) { taskType = CTaskTypes::TASK_AIM_AND_THROW_PROJECTILE; } else if (pOwnerPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_MOUNT_THROW_PROJECTILE)) { taskType = CTaskTypes::TASK_MOUNT_THROW_PROJECTILE; } if (taskType != -1) { m_taskSequenceId = pOwnerPed->GetPedIntelligence()->GetQueriableInterface()->GetTaskNetSequenceForType(taskType); if (Verifyf(m_taskSequenceId != -1, "Failed to find net sequence for task type %d", taskType) && !m_networkIdentifier.IsValid()) { // projectiles without an identifier are assigned to our machine during a network game m_networkIdentifier.Set(NetworkInterface::GetLocalPhysicalPlayerIndex(), CNetFXIdentifier::GetNextFXId()); } } } } REPLAY_ONLY(CReplayMgr::RecordObject(this)); } CProjectile::~CProjectile() { if(m_pSound) { m_pSound->StopAndForget(); } if(m_pInfo && m_pInfo->GetIsSticky()) { if(NetworkInterface::IsGameInProgress() && m_networkIdentifier.IsValid()) { CProjectileManager::RemoveNetSyncProjectile(this); } } // finish any proximity effect g_vfxProjectile.RemovePtFxProjProximity(this); // finish any fuse effect if(GetInfo()->GetFuseFxHashName().GetHash()!=0 || GetInfo()->GetFuseFirstPersonFxHashName().GetHash()!=0) { g_vfxProjectile.RemovePtFxProjFuse(this, true, true); } RestoreDamping(); } bool CProjectile::sm_bProximityMineUseActivationSafeMode = true; bool CProjectile::sm_bProximityMineUseBetterVehicleDetection = true; float CProjectile::sm_fOppressor2MissilePitchYawRollClampOverride = 0.0f; float CProjectile::sm_fOppressor2MissileTurnRateModifierOverride = 0.0f; void CProjectile::InitTunables() { sm_bProximityMineUseActivationSafeMode = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("PROX_MINE_USE_ACTIVATION_SAFE_MODE", 0xFE51FFC7), sm_bProximityMineUseActivationSafeMode); sm_bProximityMineUseBetterVehicleDetection = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("PROX_MINE_USE_BETTER_VEHICLE_DETECTION", 0x4785C94E), sm_bProximityMineUseBetterVehicleDetection); sm_fOppressor2MissilePitchYawRollClampOverride = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("OPPRESSOR2_MISSILE_PITCH_YAW_CLAMP", 0xBC857F81), sm_fOppressor2MissilePitchYawRollClampOverride); sm_fOppressor2MissileTurnRateModifierOverride = ::Tunables::GetInstance().TryAccess(CD_GLOBAL_HASH, ATSTRINGHASH("OPPRESSOR2_MISSILE_TURN_RATE_MODIFIER", 0x794AFEEA), sm_fOppressor2MissileTurnRateModifierOverride); } bool CProjectile::ProcessControl() { // Reset the reset flags m_iFlags.SetAllFlags(m_iFlags.GetAllFlags() & ~PRF_ResetMask); m_fAge += fwTimer::GetTimeStep(); if(m_iFlags.IsFlagSet(PF_UsingDestroyTimer)) { if(m_uDestroyTime <= fwTimer::GetTimeInMilliseconds()) { Displayf("CProjectile::ProcessControl: Destroy(). Reason: PF_UsingDestroyTimer: m_uDestroyTime <= fwTimer::GetTimeInMilliseconds."); Destroy(); return true; } } if(m_iFlags.IsFlagSet(PF_UsingExplodeTimer)) { if(m_uExplodeTime <= fwTimer::GetTimeInMilliseconds()) { Displayf("CProjectile::ProcessControl: Explode(). Reason: PF_UsingExplodeTimer: m_uExplodeTime <= fwTimer::GetTimeInMilliseconds."); Explode(VEC3V_TO_VECTOR3(GetTransform().GetPosition()), m_vDir, m_pCollisionEntity, m_pCollisionEntity ? true : false, 0); return true; } } // If the projectile has been thrown from a gun-aim throw don't allow it to be detonated until the player has released the detonate button if (!m_bCanDetonateInstantly) { CPed *pOwnerPed = static_cast(m_pOwner.Get()); if (pOwnerPed && pOwnerPed->IsLocalPlayer() && pOwnerPed->GetControlFromPlayer() && pOwnerPed->GetControlFromPlayer()->GetPedThrowGrenade().IsReleased()) { m_bCanDetonateInstantly = true; } } if(m_iFlags.IsFlagSet(PF_Active)) { if (m_fLifeTimeAfterExplosion > 0.0f) { // Tick down the LifeTimeAfterExplosion lifetime timer m_fLifeTimeAfterExplosion -= fwTimer::GetTimeStep(); if (m_fLifeTimeAfterExplosion <= 0.0f) { Displayf("CProjectile::ProcessControl: Destroy(). Reason: m_fLifeTimeAfterExplosion <= 0.0f."); Destroy(); } return true; } if(m_iFlags.IsFlagSet(PF_UsingLifeTimeAfterImpact)) { // Tick down the impact lifetime timer m_fLifeTimeAfterImpact -= fwTimer::GetTimeStep(); if (GetInfo()->GetShouldStickToPeds() && !m_iFlags.IsFlagSet(PF_StuckToPed)) { // Check if we have expired if(m_fLifeTimeAfterImpact <= 0.0f) { if (FadeOutProjectile()) { Displayf("CProjectile::ProcessControl: Destroy(). Reason: GetInfo()->GetShouldStickToPeds() && !m_iFlags.IsFlagSet(PF_StuckToPed): m_fLifeTimeAfterImpact <= 0.0f."); Destroy(); return true; } } } else { // Check if we have expired if(m_fLifeTimeAfterImpact <= 0.0f) { Displayf("CProjectile::ProcessControl: TriggerExplosion(). Reason: m_fLifeTimeAfterImpact <= 0.0f."); TriggerExplosion(); return true; } CreatePotentialBlastEvent(m_fLifeTimeAfterImpact); } } if(m_iFlags.IsFlagSet(PF_UsingLifeTimer)) { // Tick down the explosion timer m_fExplosionTime -= fwTimer::GetTimeStep(); if(!m_iFlags.IsFlagSet(PF_ExplosionTriggered)) { // Check if we have expired if(m_fExplosionTime <= 0.0f) { Displayf("CProjectile::ProcessControl: TriggerExplosion(). Reason: m_fExplosionTime <= 0.0f."); TriggerExplosion(); return true; } } // Tick down the lifetime timer m_fLifeTime -= fwTimer::GetTimeStep(); // Check if we have expired if(m_fLifeTime <= 0.0f) { // Projectile lifetime has expired and has no explosion (was only using trail vfx), so clean it up after a delay (5s) if (GetInfo()->GetHash() == AMMOTYPE_DLC_FLAREGUN && m_fLifeTime <= -5.0f) { Displayf("CProjectile::ProcessControl: Destroy(). Reason: GetInfo()->GetHash() == AMMOTYPE_DLC_FLAREGUN && m_fLifeTime <= -5.0f."); Destroy(); return true; } TriggerExplosion(); return true; } if(!m_iFlags.IsFlagSet(PF_ExplosionTriggered) && (m_fExplosionTime > 0.0f) && (m_fLifeTime > 0.0f)) { CreatePotentialBlastEvent(Min(m_fExplosionTime, m_fLifeTime)); } } // Explode the projectile if it's entered an air defence zone. u8 uDefenceZoneIndex = 0; if (CAirDefenceManager::IsPositionInDefenceZone(GetTransform().GetPosition(), uDefenceZoneIndex) && !m_bUseAirDefenceExplosion) { #if __BANK if (CAirDefenceManager::ShouldRenderDebug()) { grcDebugDraw::Sphere(GetTransform().GetPosition(), 0.05f, Color_blue, true, 100); } #endif // __BANK CAirDefenceZone *pDefZone = CAirDefenceManager::GetAirDefenceZone(uDefenceZoneIndex); if (pDefZone && pDefZone->IsEntityTargetable(m_pOwner)) { if (pDefZone->ShouldFireWeapon()) { // Shoot at the impact position. Vec3V vFiredFromPosition(V_ZERO); CAirDefenceManager::FireWeaponAtPosition(uDefenceZoneIndex, GetTransform().GetPosition(), vFiredFromPosition); // Only trigger explosion on local machine (explosion is synced). if (!NetworkUtils::IsNetworkClone(m_pOwner)) { // Use overridden explosion vfx and fire direction. m_bUseAirDefenceExplosion = true; m_uTimeAirDefenceExplosionTriggered = fwTimer::GetTimeInMilliseconds(); m_vAirDefenceFireDirection = VEC3V_TO_VECTOR3(Normalize(vFiredFromPosition - GetTransform().GetPosition())); } } else { // Just trigger simple instant explosion of projectile on local machine if (!NetworkUtils::IsNetworkClone(m_pOwner)) { TriggerExplosion(); return true; } } } } else if (m_bUseAirDefenceExplosion && (fwTimer::GetTimeInMilliseconds() - m_uTimeAirDefenceExplosionTriggered) > CAirDefenceManager::GetExplosionDetonationTime()) { weaponDisplayf("CProjectile::ProcessControl: TriggerExplosion(). Reason: CAirDefenceManager::IsPositionInDefenceZone."); TriggerExplosion(); return true; } // B*1893038: Destroy projectile if stuck to respawning ped if (!m_networkIdentifier.IsClone() && NetworkInterface::IsGameInProgress() && m_pStickEntity && m_pStickEntity.Get()->GetIsTypePed()) { const CPed *pStuckPed = static_cast(m_pStickEntity.Get()); if (pStuckPed && pStuckPed->GetNetworkObject() && (static_cast(pStuckPed->GetNetworkObject()))->GetRespawnInvincibilityTimer()>0) { weaponDisplayf("CProjectile::ProcessControl: Destroy(). Reason: GetRespawnInvincibilityTimer()>0."); Destroy(); return true; } } // B*1894180: Destroy projectile if it's stuck to ped in spectator mode if (NetworkInterface::IsGameInProgress() && m_pStickEntity ) { if(m_pStickEntity.Get()->GetIsTypePed()) { const CPed *pPed = static_cast(m_pStickEntity.Get()); if (pPed && pPed->IsAPlayerPed()) { CNetObjPlayer* pNetPlayer = pPed->GetNetworkObject() ? SafeCast(CNetObjPlayer, pPed->GetNetworkObject()) : 0; if (pNetPlayer && pNetPlayer->IsSpectating()) { Displayf("CProjectile::ProcessControl: Destroy(). Reason: IsSpectating()."); m_iFlags.SetFlag(PF_StuckToSpectatorPedOrGhostVeh); Destroy(); return true; } } } else if(m_pStickEntity.Get()->GetIsTypeVehicle()) { if(GetOwner() && NetworkInterface::IsDamageDisabledInMP(*m_pStickEntity.Get(), *GetOwner())) { m_iFlags.SetFlag(PF_StuckToSpectatorPedOrGhostVeh); } else { m_iFlags.ClearFlag(PF_StuckToSpectatorPedOrGhostVeh); } } } bool bPassiveMode = false; if(NetworkInterface::IsGameInProgress()) { if (m_pOwner && m_pOwner->GetIsTypePed()) { CPed* pOwnerPed = static_cast(m_pOwner.Get()); //! Also, if owner goes passive, prevent prox mines detonating as this could be exploited. if(pOwnerPed->IsPlayer() && pOwnerPed->GetNetworkObject()) { CNetObjPlayer* pOwnerNetObjPlayer = static_cast(pOwnerPed->GetNetworkObject()); if (pOwnerNetObjPlayer && pOwnerNetObjPlayer->IsPassiveMode()) { bPassiveMode = true; } } } if (m_pStickEntity && m_pStickEntity->GetIsTypePed()) { CPed* pStickyPed = static_cast(m_pStickEntity.Get()); if(pStickyPed->IsPlayer() && pStickyPed->GetNetworkObject()) { CNetObjPlayer* pStickyNetObjPlayer = static_cast(pStickyPed->GetNetworkObject()); if (pStickyNetObjPlayer && pStickyNetObjPlayer->IsPassiveMode()) { bPassiveMode = true; } } if(GetOwner() && NetworkInterface::IsDamageDisabledInMP(*pStickyPed, *GetOwner())) { bPassiveMode = true; } if(bPassiveMode) { DetachFromStickEntity(); } } } // B*2148142: Proximity Mines - only detonate if stuck and enemy ped steps within the bomb explosion radius and has LOS to the bomb. Don't do this if a cutscene is running! if(!bPassiveMode && GetInfo()->GetIsProximityDetonation() && m_iFlags.IsFlagSet(PF_Sticked) && !CutSceneManager::GetInstance()->IsCutscenePlayingBack()) { bool bProximityExploded = ProcessProximityMine(); if (bProximityExploded) { return true; } } // Destroy the projectile if it is outside the projectile ceiling Vec3V vNewProjectilePosition = GetTransform().GetPosition(); vNewProjectilePosition += VECTOR3_TO_VEC3V(GetVelocity() * fwTimer::GetTimeStep()); if(!g_WorldLimits_AABB.ContainsPoint(vNewProjectilePosition) || vNewProjectilePosition.GetZf() - PROJECTILE_CEILING_PLANE > 0.0f ) { #if !__NO_OUTPUT if(vNewProjectilePosition.GetZf() - PROJECTILE_CEILING_PLANE <= 0.0f) { weaponWarningf("CProjectile is going outside of world limits. Position:%f,%f,%f, PF_UsingLifeTimer:%i, Script Controlled:%i, Name:%s", vNewProjectilePosition.GetXf(), vNewProjectilePosition.GetYf(), vNewProjectilePosition.GetZf(), m_iFlags.IsFlagSet(PF_UsingLifeTimer), GetIsScriptControlled(), GetInfo()->GetName()); } #endif //__NO_OUTPUT Displayf("CProjectile::ProcessControl: TriggerExplosion(). Reason: g_WorldLimits_AABB."); m_iFlags.SetFlag(PF_ForceExplosion); TriggerExplosion(); return true; } if(NetworkInterface::IsGameInProgress()) { // Hack! Destroy sticky projectiles at the origin! const CAmmoProjectileInfo* pAmmoInfo = GetInfo(); if(pAmmoInfo->GetIsSticky()) { Vector3 vCurPos = VEC3V_TO_VECTOR3(vNewProjectilePosition); static const Vector3 vOrigin = VEC3_ZERO; if(vCurPos.IsClose(vOrigin, 0.2f)) { Assertf(0, "CProjectile::ProcessControl: Deleting sticky projectile at the origin!"); Destroy(); return true; } } } // Trigger the explosion and start ticking down the lifetime timer. if(m_iFlags.IsFlagSet(PF_ExplodeNextFrame) && !m_iFlags.IsFlagSet(PF_ExplosionTriggered)) { Displayf("CProjectile::ProcessControl: TriggerExplosion(). Reason: m_iFlags.IsFlagSet(PF_ExplodeNextFrame) && !m_iFlags.IsFlagSet(PF_ExplosionTriggered)."); m_iFlags.SetFlag(PF_ForceExplosion); TriggerExplosion(); m_fLifeTime -= m_fExplosionTime; m_iFlags.SetFlag(PF_UsingLifeTimer); m_iFlags.ClearFlag(PF_ExplodeNextFrame); } // Attract nearby homing missiles if they're close enough ProcessHomingAttractor(); // Handle entering water ProcessInWater(); // Process the audio ProcessAudio(); // Process WhizzBy Events ProcessWhizzByEvents(); } // Process the effects ProcessEffects(); // B*1909349 - Hide projectiles that are stuck to peds in a vehicle to prevent clipping with seat (except bikes etc). if (m_pStickEntity && m_pStickEntity->GetIsTypePed()) { const CPed *pPed = static_cast(m_pStickEntity.Get()); if (pPed && pPed->GetIsInVehicle() && pPed->GetVehiclePedInside()) { const CVehicle *pVehicle = static_cast(pPed->GetVehiclePedInside()); if (pVehicle) { bool bIsInVehicleExclusions = pVehicle->InheritsFromBike() || pVehicle->InheritsFromQuadBike() || pVehicle->InheritsFromAmphibiousQuadBike() || pVehicle->GetIsJetSki(); if (!bIsInVehicleExclusions && GetIsVisibleForModule(SETISVISIBLE_MODULE_GAMEPLAY)) { m_bHideStuckProjectileInVehicle = true; SetIsVisibleForModule(SETISVISIBLE_MODULE_GAMEPLAY, false, true); } } } else if (!GetInfo()->GetShouldHideDrawable() && !pPed->GetIsInVehicle() && m_bHideStuckProjectileInVehicle) { m_bHideStuckProjectileInVehicle = false; SetIsVisibleForModule(SETISVISIBLE_MODULE_GAMEPLAY, true, true); } } // Base class return CObject::ProcessControl(); } void CProjectile::ApplyDeformation(const CVehicle *pAttachedVehicle, const void* basePtr, bool bInit) { if(pAttachedVehicle && basePtr) { int iBoneIndex = GetHitBoneIndexFromFrag(); const crSkeletonData& skeletonData = pAttachedVehicle->GetSkeletonData(); Matrix34 boneMat; if(const crBoneData* pBoneData = skeletonData.GetBoneData(iBoneIndex)) { // Use default translation because the wheels local matrix gets messed around with for rendering Quaternion defaultRotation = RCC_QUATERNION(pBoneData->GetDefaultRotation()); boneMat.FromQuaternion(defaultRotation); boneMat.d = RCC_VECTOR3(pBoneData->GetDefaultTranslation()); pBoneData = pBoneData->GetParent(); while(pBoneData) { const Matrix34& matParent = pAttachedVehicle->GetLocalMtx(pBoneData->GetIndex()); boneMat.Dot(matParent); pBoneData = pBoneData->GetParent(); } } else { boneMat = pAttachedVehicle->GetLocalMtx(iBoneIndex); } if(fwAttachmentEntityExtension *extension = GetAttachmentExtension()) { Vector3 vOffset; boneMat.Transform(extension->GetAttachOffset(), vOffset); if(bInit) { m_vStickDeformation = VEC3V_TO_VECTOR3(pAttachedVehicle->GetVehicleDamage()->GetDeformation()->ReadFromVectorOffset(basePtr, VECTOR3_TO_VEC3V(vOffset))); } else { vOffset -= m_vStickDeformation; Vector3 vNewDeformation = VEC3V_TO_VECTOR3(pAttachedVehicle->GetVehicleDamage()->GetDeformation()->ReadFromVectorOffset(basePtr, VECTOR3_TO_VEC3V(vOffset))); if(!vNewDeformation.IsClose(m_vStickDeformation, 0.001f)) { vOffset += vNewDeformation; boneMat.UnTransform(vOffset); extension->SetAttachOffset(vOffset); m_vStickDeformation = vNewDeformation; } } } } } ePhysicsResult CProjectile::ProcessPhysics(float fTimeStep, bool bCanPostpone, s32 iTimeSlice) { // GTAV - B*1790836 - Just return physics done if the physics inst isn't in the level. if( GetCurrentPhysicsInst()->GetLevelIndex() == phInst::INVALID_INDEX ) { return PHYSICS_DONE; } if( m_iFlags.IsFlagSet(PF_Active) ) { // make sure the grenade from grenade launcher play straight. // X axis is forward for this specific model (w_lr_40mm) phCollider* pCollider = GetCollider(); if(pCollider && m_pInfo && (m_pInfo->GetAlignWithTrajectory() || m_pInfo->GetAlignWithTrajectoryYAxis())) { Vec3V vVelocity = pCollider->GetVelocity(); ScalarV vVelocityMagSquared = MagSquared(vVelocity); ScalarV MINIMUM_VELOCITY_SQUARED(V_ONE); if (IsTrue(vVelocityMagSquared > MINIMUM_VELOCITY_SQUARED)) { Vec3V vMotionDirection = vVelocity * InvSqrt(vVelocityMagSquared); Vec3V vAimDirection = m_pInfo->GetAlignWithTrajectoryYAxis() ? GetMatrix().b() : GetMatrix().a(); Vec3V vCrossProduct = Cross(vAimDirection, vMotionDirection); ScalarV sSine = Mag(vCrossProduct); BoolV bTooSmall = sSine < ScalarV(V_FLT_MIN); Vec3V vRotationAxis = SelectFT(bTooSmall, InvScaleSafe(vCrossProduct, sSine), Vec3V(V_ZERO)); ScalarV sAngle = SelectFT(bTooSmall, Arcsin(Min(ScalarV(V_ONE), sSine)), ScalarV(V_ZERO)); Vec3V vAngVel = vRotationAxis * sAngle * Invert(ScalarV(fTimeStep)); Assertf(IsFiniteAll(vAngVel), "Projectile Angular velocity is invalid. timestep = %f angle = %f rotation axis = %f %f %f",fTimeStep, sAngle.Getf(), vRotationAxis.GetXf(), vRotationAxis.GetYf(), vRotationAxis.GetZf()); pCollider->SetAngVelocity(RCC_VECTOR3(vAngVel)); } } Vector3 vPos = CFocusEntityMgr::GetMgr().GetPos(); // Determine which include flags to use based on distance float fDistToFocusSq = vPos.Dist2(VEC3V_TO_VECTOR3(GetTransform().GetPosition())); if (GetIsAttached()) { CPhysics::GetLevel()->SetInstanceIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex(), u32(ArchetypeFlags::GTA_BASIC_ATTACHMENT_INCLUDE_TYPES)); } else if(fDistToFocusSq < square(WEAPON_BOUNDS_PRESTREAM)) { CPhysics::GetLevel()->SetInstanceTypeAndIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex(), ArchetypeFlags::GTA_PROJECTILE_TYPE, ArchetypeFlags::GTA_PROJECTILE_NEAR_INCLUDE_TYPES); } else { CPhysics::GetLevel()->SetInstanceTypeAndIncludeFlags(GetCurrentPhysicsInst()->GetLevelIndex(), ArchetypeFlags::GTA_PROJECTILE_TYPE, ArchetypeFlags::GTA_PROJECTILE_INCLUDE_TYPES); } } if( m_iFlags.IsFlagSet( PF_Sticked ) && m_pStickEntity ) { if( m_pStickEntity->GetIsPhysical() ) { CPhysical* pHitPhysical = static_cast( m_pStickEntity.Get() ); fragInst* pFragInst = pHitPhysical->GetFragInst(); if( pFragInst ) { const float fProjectileWidth = GetBoundingBoxMax().GetZ() - GetBoundingBoxMin().GetZ(); // Check for broken glass if( pHitPhysical->GetIsTypeVehicle() ) { phBoundComposite* pBoundComposite = static_cast( pFragInst->GetArchetype()->GetBound() ); if( pBoundComposite ) { // If this vehicle part is smashed then don't try to attach CVehicle* pVehicle = static_cast( pHitPhysical ); if( pVehicle->IsHiddenFlagSet( m_iStickComponent ) ) { // Detect how much has broken and when we overlap, detach from my parent entity spdSphere projectileSphere; projectileSphere.Set( RCC_VEC3V( m_vStickPos ), ScalarV( fProjectileWidth ) ); spdSphere smashSphere( CVehicleGlassManager::GetVehicleGlassComponentSmashSphere( pHitPhysical, m_iStickComponent, false ) ); if( projectileSphere.IntersectsSphere( smashSphere ) ) { DetachFromStickEntity(); } } // Ensure we're still attached if(m_pStickEntity) { // Detach if we're on a window that's rolling/rolled down const CVehicle* pStickVehicle = static_cast(pVehicle); for(int windowHierarchyId = VEH_FIRST_WINDOW; windowHierarchyId < VEH_LAST_WINDOW; ++windowHierarchyId) { if(pVehicle->GetFragmentComponentIndex((eHierarchyId)windowHierarchyId) == m_iStickComponent) { bool windowRollingDown = false; if(const CConvertibleRoofWindowInfo* pWindowExtension = pVehicle->GetBaseModelInfo()->GetExtension()) { windowRollingDown = (pStickVehicle->GetConvertibleRoofProgress() != 0.0f) && pWindowExtension->ContainsThisWindowId((eHierarchyId)windowHierarchyId); } if(pVehicle->IsWindowDown((eHierarchyId)windowHierarchyId) || windowRollingDown) { DetachFromStickEntity(); } break; } } } } } // Check for non-vehicle cracked glass // Note: There is no easy way to else if( pFragInst->GetCached() && pFragInst->GetCacheEntry()->GetHierInst() && m_iStickComponent < pFragInst->GetTypePhysics()->GetNumChildren() && pFragInst->GetChildBroken( m_iStickComponent ) ) { Vec3V vStartPosition = GetTransform().GetPosition(); Vec3V vEndPosition = vStartPosition + Scale( Negate( GetTransform().GetC() ), ScalarV( fProjectileWidth ) ); WorldProbe::CShapeTestCapsuleDesc capsuleDesc; WorldProbe::CShapeTestSingleResult capsuleResults; capsuleDesc.SetResultsStructure( &capsuleResults ); capsuleDesc.SetCapsule( VEC3V_TO_VECTOR3( vStartPosition ), VEC3V_TO_VECTOR3( vEndPosition ), fProjectileWidth ); capsuleDesc.SetExcludeEntity( this ); capsuleDesc.SetIncludeFlags( ArchetypeFlags::GTA_GLASS_TYPE ); capsuleDesc.SetTypeFlags( ArchetypeFlags::GTA_WEAPON_TEST ); capsuleDesc.SetIsDirected( true ); capsuleDesc.SetTreatPolyhedralBoundsAsPrimitives( false ); if( !WorldProbe::GetShapeTestManager()->SubmitTest( capsuleDesc ) ) DetachFromStickEntity(); } } } } // B*2225632: Flare Gun - Lerp down the X/Y velocity if it's trajectory is close to the max pitch limits. // Allows players to shoot flares upwards which land near their original firing position. const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo(m_uWeaponFiredFromHash); if (pWeaponInfo && pWeaponInfo->GetHash() == ATSTRINGHASH("WEAPON_FLAREGUN", 0x47757124)) { const CAimingInfo* pAimingInfo = pWeaponInfo->GetAimingInfo(); if (pAimingInfo) { // Calculate the projectile pitch Vector3 vVelocity = GetVelocity(); float fProjectilePitch = 0.0f; Vector2 vDir; vVelocity.GetVector2XY(vDir); const float fVelMag = vVelocity.Mag(); if (fVelMag != 0.0f) { fProjectilePitch = Sign(vVelocity.z) * rage::AcosfSafe(vDir.Mag() / fVelMag); fProjectilePitch = fwAngle::LimitRadianAngleForPitch(fProjectilePitch); const float fSweepPitchMax = pAimingInfo->GetSweepPitchMax() * DtoR; static dev_float fMaxPitchScaler = 0.95f; const float fPitchLimit = fSweepPitchMax * fMaxPitchScaler; // Is projectile pitch over the limit? if (fProjectilePitch > fPitchLimit) { // Lerp down X/Y velocity down to zero. // Scale lerp rate based on delta over pitch limit. static dev_float fMaxLerpRate = 0.025f; // Max rate to use if aiming along max pitch limit static dev_float fMinLerpRate = 0.001f; // Min rate to use if aiming along min pitch limit const float fPitchLimitsDelta = fSweepPitchMax - fPitchLimit; const float fActualPitchDelta = fSweepPitchMax - fProjectilePitch; float fLerpRateMult = fActualPitchDelta / fPitchLimitsDelta; float fLerpRate = fMaxLerpRate - (fLerpRateMult * fMaxLerpRate); fLerpRate = rage::Clamp(fLerpRate, fMinLerpRate, fMaxLerpRate); vVelocity.x = rage::Lerp(fLerpRate, vVelocity.x, 0.0f); vVelocity.y = rage::Lerp(fLerpRate, vVelocity.y, 0.0f); SetVelocity(vVelocity); } } } } //return PHYSICS_DONE; return CObject::ProcessPhysics(fTimeStep, bCanPostpone, iTimeSlice); } void CProjectile::ProcessCollision(phInst const * myInst, CEntity* pHitEnt, phInst const* hitInst, const Vector3& vMyHitPos, const Vector3& vOtherHitPos, float fImpulseMag, const Vector3& vMyNormal, int iMyComponent, int iOtherComponent, phMaterialMgr::Id iOtherMaterial, bool bIsPositiveDepth, bool bIsNewContact) { if(pHitEnt && pHitEnt != m_pOwner) { phIntersection intersection; intersection.Set(hitInst->GetLevelIndex(), PHLEVEL->GetGenerationID(hitInst->GetLevelIndex()),RCC_VEC3V(vOtherHitPos), RCC_VEC3V(vMyNormal), 0.0f, 1.0f, 0, static_cast(iOtherComponent), iOtherMaterial); ProcessImpact(intersection); } CObject::ProcessCollision(myInst, pHitEnt, hitInst, vMyHitPos, vOtherHitPos, fImpulseMag, vMyNormal, iMyComponent, iOtherComponent, iOtherMaterial, bIsPositiveDepth, bIsNewContact); } void CProjectile::OnActivate(phInst* pInst, phInst* pOtherInst) { // Must be called first or GetCollider won't work correctly CObject::OnActivate(pInst, pOtherInst); const CAmmoProjectileInfo* pAmmoInfo = GetInfo(); if (pAmmoInfo->GetDoubleDamping()) { if (phCollider* collider = GetCollider()) { collider->SetDoubleDampingEnabled(true); } } } void CProjectile::PostPreRender() { if (m_pInfo==NULL) { return; } // lights bool lightActive = m_lightBone>-1; if (m_pInfo->GetLightOnlyActiveWhenStuck() && m_iFlags.IsFlagSet(PF_Sticked)==false) { lightActive = false; } if (m_iFlags.IsFlagSet(PF_TrailInactive) && !m_iFlags.IsFlagSet(PF_Sticked)) { lightActive = false; } if (!GetIsVisible()) { lightActive = false; } if (GetInfo()->GetIsProximityDetonation() && !m_bProximityMineActive) { lightActive = false; } if (m_iFlags.IsFlagSet(PF_Exploded)) { lightActive = false; } if (lightActive) { Matrix34 lightMatrix; GetGlobalMtx(m_lightBone, lightMatrix); Vector3 pos = lightMatrix.d; float time = (float)(CNetwork::GetSyncedTimeInMilliseconds() + (u32)GetRandomSeed()); float mult = 1.0f; bool bUsingTimeStep = false; if (m_iFlags.IsFlagSet(PF_UsingLifeTimer) && m_pInfo->GetLightSpeedsUp()) { TUNE_GROUP_FLOAT(EXPLOSIVE_TUNE, FLASH_START_PHASE, 0.5f, 0.0f, 1.0f, 0.01f); TUNE_GROUP_FLOAT(EXPLOSIVE_TUNE, FLASH_END_MULT, 3.0f, 1.0f, 5.0f, 0.01f); float fTimeMult = 1.0f; float fPhase = 1 - (m_fLifeTime / m_pInfo->GetLifeTime()); if (fPhase > FLASH_START_PHASE) { float fDelta = (fPhase - FLASH_START_PHASE) / (1 - FLASH_START_PHASE); fTimeMult = 1.0f + ((FLASH_END_MULT - 1.0f) * fDelta); } bUsingTimeStep = true; m_fTimeStepTimer += (fwTimer::GetTimeStep() * 1000 * fTimeMult); } // B*2148142: Proximity mine has been triggered, flash rapidly until it explodes. if (m_bProximityMineTriggered) { bUsingTimeStep = true; m_fTimeStepTimer += (fwTimer::GetTimeStep() * 1000 * m_pInfo->GetProximityLightFrequencyMultiplierTriggered()); } if (m_pInfo->GetLightFrequency()>0.0f) { float fSin = Sinf((bUsingTimeStep ? m_fTimeStepTimer : time) * m_pInfo->GetLightFrequency()); mult = powf((fSin + 1.0f) / 2.0f, m_pInfo->GetLightPower()); } if (m_pInfo->GetLightFlickers() && !fwTimer::IsGamePaused()) { mult = g_DrawRand.GetRanged(0.5f, 1.0f); } Vec3V vLightColour = m_pInfo->GetLightColour(); // Proximity mine: use alternate light colour while untriggered. if (GetInfo()->GetIsProximityDetonation() && !m_bProximityMineTriggered) { Vec3V vUntriggeredColour = m_pInfo->GetProximityLightColourUntriggered(); if (!IsEqualAll(vUntriggeredColour, Vec3V(V_ZERO))) { vLightColour = vUntriggeredColour; } } CLightSource light(LIGHT_TYPE_POINT, LIGHTFLAG_FX | LIGHTFLAG_NO_SPECULAR, pos, VEC3V_TO_VECTOR3(vLightColour), m_pInfo->GetLightIntensity() * mult, LIGHT_ALWAYS_ON); light.SetRadius(m_pInfo->GetLightRange()); light.SetInInterior(GetInteriorLocation()); light.SetFalloffExponent(m_pInfo->GetLightFalloffExp()); Lights::AddSceneLight(light); Color32 col(vLightColour); g_coronas.Register(RCC_VEC3V(pos), m_pInfo->GetCoronaSize(), col, m_pInfo->GetCoronaIntensity() * mult, m_pInfo->GetCoronaZBias(), Vec3V(V_X_AXIS_WZERO), 0.0f, 0.0f, 0.0f, CORONA_DONT_REFLECT); if(m_pInfo->GetLightOnlyActiveWhenStuck()) { if(mult > m_fLightIntensityMult) { if(!m_iFlags.IsFlagSet(PRF_LightIntensityGrowing)) { // Trigger a beep on each cycle of the light flashing m_iFlags.SetFlag(PRF_TriggerBeep); m_iFlags.SetFlag(PRF_LightIntensityGrowing); } } else if(mult < m_fLightIntensityMult) { m_iFlags.ClearFlag(PRF_LightIntensityGrowing); } } m_fLightIntensityMult = mult; } else { m_fLightIntensityMult = 0.0f; } } //Use these if .dat values haven't been filled in yet const float DEFAULT_SHOOT_FORCE = 50.0f; const float DEFAULT_FRAG_IMPULSE = 100.0f; void CProjectile::ProcessImpact(phIntersection& intersection) { phInst* pOtherInst = intersection.GetInstance(); CEntity* pHitEntity = CPhysics::GetEntityFromInst( pOtherInst ); if( pHitEntity ) { const CAmmoProjectileInfo* pAmmoInfo = GetInfo(); const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo(m_uWeaponFiredFromHash); bool bIsGlass = false; if (CPhysics::GetLevel()->GetInstanceTypeFlags( pOtherInst->GetLevelIndex() ) & ArchetypeFlags::GTA_GLASS_TYPE) { bIsGlass = true; phBound* pOtherBound = pOtherInst->GetArchetype()->GetBound(); if (pOtherBound->GetType() == phBound::COMPOSITE) { phBoundComposite* pOtherComposite = static_cast(pOtherBound); if (u32* pTypeAndIncludeFlags = pOtherComposite->GetTypeAndIncludeFlags()) { bIsGlass = (pOtherComposite->GetTypeFlags(pTypeAndIncludeFlags, intersection.GetComponent()) & ArchetypeFlags::GTA_GLASS_TYPE) != 0; } } } if( bIsGlass && !pAmmoInfo->GetIsSticky() && pWeaponInfo && pHitEntity->GetIsTypeObject() && pOtherInst->GetClassType() == PH_INST_FRAG_OBJECT ) { float fShootForce = pWeaponInfo->GetForce() > 0 ? pWeaponInfo->GetForce() : DEFAULT_SHOOT_FORCE; float fFragImpulse = pWeaponInfo->GetFragImpulse() > 0 ? pWeaponInfo->GetFragImpulse() : DEFAULT_FRAG_IMPULSE; fragInst *fInst = pHitEntity->GetFragInst(); if(fInst) { int component = intersection.GetComponent(); fragTypeChild* child = fInst->GetTypePhysics()->GetAllChildren()[component]; int groupIndex = child->GetOwnerGroupPointerIndex(); fragTypeGroup *group = fInst->GetTypePhysics()->GetAllGroups()[groupIndex]; if (group->GetMadeOfGlass() && group->GetStrength() > 0.0f) { fShootForce = group->GetStrength(); if(fShootForce >= fFragImpulse) fFragImpulse += fShootForce; } Vector3 vForce = VEC3V_TO_VECTOR3(intersection.GetPosition() - GetMatrix().d()); vForce.Normalize(); vForce *= fShootForce; CObject *pHitObject = (CObject *)pHitEntity; //Set Crack Id for Explosions on Glass s32 crackId = 0; if(m_effectGroup >= 0) { crackId = g_vfxWeapon.GetBreakableGlassId(m_effectGroup); } CPhysics::SetSelectedCrack(crackId); pHitObject->ApplyExternalImpulse( vForce, VEC3V_TO_VECTOR3(intersection.GetPosition() - pHitEntity->GetTransform().GetPosition()), intersection.GetComponent(), intersection.GetPartIndex(), pOtherInst, fFragImpulse ); } } // Don't allow sticky bombs to fall asleep when touching peds (and not attached to them) -- peds can balance the sticky bomb and cause it to freeze mid-air if(pAmmoInfo->GetIsSticky() && pHitEntity && pHitEntity->GetIsTypePed() && !GetIsAttached()) { phCollider* pCollider = GetCollider(); if(pCollider) { phSleep* pSleep = pCollider->GetSleep(); if(pSleep) { pSleep->Reset(); } } } } } void CProjectile::ProcessPreComputeImpacts(phContactIterator impacts) { const CAmmoProjectileInfo* pAmmoInfo = GetInfo(); const bool bIsFlare = ( pAmmoInfo->GetHash() == atHashWithStringNotFinal("AMMO_FLAREGUN") ); impacts.Reset(); while( !impacts.AtEnd() ) { // If this impact is from a constraint, let it pass through. One scenario is that when ped is holding an grenade while switching to ragdoll if(impacts.GetRootManifold().IsConstraint()) { impacts++; continue; } // Should we even process impacts for this ammo type? // or are we processing the collision via shape tests? if( !pAmmoInfo->GetShouldProcessImpacts() || m_iFlags.IsFlagSet( PF_ProcessCollisionProbe ) ) { impacts.DisableImpact(); impacts++; continue; } RestoreDamping(); if(m_bNetworkHasHitPlayer) { Assertf(NetworkInterface::IsGameInProgress(),"Only expect m_bNetworkHasHitPlayer to be used in net games"); impacts.SetElasticity(0.0f); // Make it harder to bounce off and away from players B*2031735 } phInst* pOtherInst = impacts.GetOtherInstance(); if(pOtherInst) { CEntity* pHitEntity = CPhysics::GetEntityFromInst( pOtherInst ); if (pHitEntity) { if(GetOwner() && NetworkInterface::IsDamageDisabledInMP(*pHitEntity, *GetOwner())) { impacts.DisableImpact(); impacts++; continue; } else if( m_ignoreDamageEntityAttachParent && m_pIgnoreDamageEntity && pHitEntity->GetAttachParent() == m_pIgnoreDamageEntity ) { impacts.DisableImpact(); impacts++; continue; } else if(pHitEntity->GetIsTypeVehicle()) { CVehicle *pVehicle = static_cast(pHitEntity); if(pVehicle->InheritsFromBike() || pVehicle->InheritsFromQuadBike() || pVehicle->InheritsFromAmphibiousQuadBike()) { int nOtherComponent = impacts.GetOtherComponent(); CVehicleModelInfo* pVehicleModelInfo = pVehicle->GetVehicleModelInfo(); bool bHitPedSeatBound = false; if(pVehicleModelInfo && pVehicleModelInfo->GetHasSeatCollision()) { for(int i = 0; i < pVehicle->GetSeatManager()->GetMaxSeats(); ++i) { if(pVehicle->GetSeatManager()->GetPedInSeat(i)) { s32 iFragChild = pVehicleModelInfo->GetFragChildForSeat(i); if(iFragChild == nOtherComponent) { bHitPedSeatBound = true; continue; } } } if(bHitPedSeatBound) { impacts.DisableImpact(); impacts++; continue; } } } const CEntity* pNoCollisionEntity = (const CEntity*)GetNoCollisionEntity(); // B*3648993: If the projectile is ignoring a trailer or its cab, ignore the other part of it. if (pNoCollisionEntity && GetIsEntityPartOfTrailer(pVehicle,pNoCollisionEntity)) { impacts.DisableImpact(); impacts++; continue; } } } } // Ignore all shoot through materials with an exception of glass since we want it to shatter bool bIsGlass = pOtherInst ? ( CPhysics::GetLevel()->GetInstanceTypeFlags( pOtherInst->GetLevelIndex() ) & ArchetypeFlags::GTA_GLASS_TYPE) > 0 : false; if(bIsGlass) { // If the instance is marked as being glass, ensure that we aren't hitting a non-glass part of the bound. const phBound* pOtherBound = pOtherInst->GetArchetype()->GetBound(); if(phBound::IsTypeComposite(pOtherBound->GetType())) { const phBoundComposite* pOtherBoundComposite = static_cast(pOtherBound); if(pOtherBoundComposite->GetTypeAndIncludeFlags() && ((pOtherBoundComposite->GetTypeFlags(impacts.GetOtherComponent()) & ArchetypeFlags::GTA_GLASS_TYPE) == 0)) { bIsGlass = false; } } #if __DEV if(bIsGlass) { weaponDebugf1("CProjectile::ProcessPreComputeImpacts - Disabling '%s' collision with '%s' (component %i) because it's made of glass.", GetModelName(), pOtherInst->GetArchetype()->GetFilename(), impacts.GetOtherComponent()); } #endif // __DEV } if(!bIsGlass || pAmmoInfo->GetIsSticky()) { // Ignore impacts against car void materials bool bHitCarVoidMaterial = ( PGTAMATERIALMGR->UnpackMtlId(impacts.GetOtherMaterialId()) == PGTAMATERIALMGR->g_idCarVoid ); if( bHitCarVoidMaterial ) { #if __DEV Vec3V otherLocalPos = impacts.GetInstanceA() == impacts.GetMyInstance() ? impacts.GetContact().GetLocalPosB() : impacts.GetContact().GetLocalPosA(); weaponDebugf1("CProjectile::ProcessPreComputeImpacts - Disabling '%s' collision with '%s' (component %i, primitive %i, local pos <%f, %f, %f>) because it's car_void material..", GetModelName(), pOtherInst->GetArchetype()->GetFilename(), impacts.GetOtherComponent(), impacts.GetOtherElement(), VEC3V_ARGS(otherLocalPos)); #endif // __DEV impacts.DisableImpact(); impacts++; continue; } float fDotNormalLimit = pAmmoInfo->GetRicochetTolerance(); CEntity* pHitEntity = CPhysics::GetEntityFromInst( pOtherInst ); if( pHitEntity ) { // We should never hit the owner if( pHitEntity == m_pOwner ) { weaponDebugf1("CProjectile::ProcessPreComputeImpacts - Disabling '%s' collision with '%s' because it is the owner.", GetModelName(), pOtherInst ? pOtherInst->GetArchetype()->GetFilename() : NULL); impacts.DisableImpact(); impacts++; continue; } if(m_pOwner && m_pOwner->GetIsTypePed() ) { CPed* pOwnerPed = static_cast( m_pOwner.Get() ) ; // Ignore impacts against your own mount or vehicle if( pHitEntity == pOwnerPed->GetMyMount() || (pOwnerPed->GetPedConfigFlag( CPED_CONFIG_FLAG_InVehicle ) && pHitEntity == pOwnerPed->GetMyVehicle() && !m_bThrownFromOutsideOfVehicle) ) { weaponDebugf1("CProjectile::ProcessPreComputeImpacts - Disabling '%s' collision with '%s' because it is the owner's mount.", GetModelName(), pOtherInst ? pOtherInst->GetArchetype()->GetFilename() : NULL); impacts.DisableImpact(); impacts++; continue; } // Also ignore impacts against other occupants of the same vehicle if (pHitEntity->GetIsTypePed() && pOwnerPed->GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle)) { CPed* pHitPed = static_cast(pHitEntity); if (pHitPed->GetMyVehicle() == pOwnerPed->GetMyVehicle()) { weaponDebugf1("CProjectile::ProcessPreComputeImpacts - Disabling '%s' collision with '%s' because they are both in the same vehicle.", GetModelName(), pOtherInst ? pOtherInst->GetArchetype()->GetFilename() : NULL); impacts.DisableImpact(); impacts++; continue; } } } // B*5056893: OPPRESSOR2 missiles are hitting the driver ped's capsule when firing and driving fast in reverse (not sure why, possibly inheriting some backward velocity / lagging capsule collision) // We already set the ignore collision entity on the projectile (the vehicle) and can only have one, so here's an exception for the ped as well if (m_pOwner && m_pOwner->GetIsTypeVehicle()) { const CVehicle* pOwnerVehicle = static_cast(m_pOwner.Get()); if (MI_BIKE_OPPRESSOR2.IsValid() && pOwnerVehicle->GetModelIndex() == MI_BIKE_OPPRESSOR2) { // If the projectile still has the owner vehicle as the no collision entity, then the missile hasn't left the vehicle's bounding box yet if (pHitEntity == pOwnerVehicle->GetDriver() && GetNoCollisionEntity() == pOwnerVehicle) { weaponDebugf1("CProjectile::ProcessPreComputeImpacts - Disabling '%s' collision with '%s' because owner is OPPRESSOR2 and hit entity is driver ped.", GetModelName(), pOtherInst ? pOtherInst->GetArchetype()->GetFilename() : NULL); impacts.DisableImpact(); impacts++; continue; } } } // B*1638542 if (IsEntityInvisibleCardboardBoxAttachedToVehicle(*pHitEntity)) { weaponDebugf1("CProjectile::ProcessPreComputeImpacts - Disabling '%s' collision with '%s' because it is an invisible cardboard box attached to a vehicle.", GetModelName(), pOtherInst ? pOtherInst->GetArchetype()->GetFilename() : NULL); impacts.DisableImpact(); impacts++; continue; } // Calculate ricochet tolerance if( pHitEntity->GetIsTypeVehicle() ) { fDotNormalLimit = pAmmoInfo->GetVehicleRicochetTolerance(); impacts.SetElasticity(0.0f); // make it harder to bounce off vehicle } else if( pHitEntity->GetIsTypePed() ) { m_pHitPed = static_cast(pHitEntity); if(NetworkInterface::IsGameInProgress() && m_pHitPed->IsPlayer()) { m_bNetworkHasHitPlayer = true; // Set this so to ensure projectile will always have elasticity turned off from now on impacts.SetElasticity(0.0f); // which makes it harder to bounce off and away players. B*2031735 } m_iFlags.SetFlag(PF_HitPedThisFrame); fDotNormalLimit = pAmmoInfo->GetPedRicochetTolerance(); //B*1837611: Check if hit ped is friendly so we can determine whether or not to stick stickybombs to the peds back in CProjectile::ShouldStickToEntity if (m_pHitPed && m_pOwner && m_pOwner->GetIsTypePed()) { const CPed* pOwnerPed = static_cast( m_pOwner.Get()); if (pOwnerPed) { const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo(m_uWeaponFiredFromHash); if (pWeaponInfo) { m_bHitPedFriendly = !pOwnerPed->IsAllowedToDamageEntity(pWeaponInfo, m_pHitPed); } } } } } //If we hit a wall just after we fired from cover, we probably hit the cover if (m_iFlags.IsFlagSet(PF_FiredFromCover)) { if (!pHitEntity || !pHitEntity->GetIsTypePed()) { if(m_pOwner && m_pOwner->GetIsTypePed()) { CPed* pOwnerPed = static_cast( m_pOwner.Get() ); if (!pOwnerPed->GetPedResetFlag(CPED_RESET_FLAG_IsThrowingProjectileWhileAiming)) { Vector3 pedPos = VEC3V_TO_VECTOR3(pOwnerPed->GetTransform().GetPosition()); pedPos.Subtract(VEC3V_TO_VECTOR3(impacts.GetOtherPosition())); Vector3 projectileVel = GetVelocity(); static dev_float sf_MinTravelDistance = 2.0f; static dev_float sf_MinHorizontalVel = 4.0f; if (pedPos.XYMag() < sf_MinTravelDistance && projectileVel.XYMag() > sf_MinHorizontalVel) { Vector3 vMyNormal; impacts.GetMyNormal( vMyNormal ); if (vMyNormal.z <= 0.7f) //do hit the ground. { weaponDebugf1("CProjectile::ProcessPreComputeImpacts - Disabling '%s' collision with '%s' because we fired from cover.", GetModelName(), pOtherInst ? pOtherInst->GetArchetype()->GetFilename() : NULL); impacts.DisableImpact(); impacts++; continue; } } } } } } if(pHitEntity && pHitEntity->GetIsTypeVehicle() && ((CVehicle *)pHitEntity)->InheritsFromHeli()) { CHeli *pHitHeli = (CHeli *)pHitEntity; if(pAmmoInfo->GetIsSticky() && pHitHeli->DoesBulletHitPropellerBound(impacts.GetOtherComponent())) { impacts.DisableImpact(); impacts++; continue; } if(pHitHeli->IsPropeller(impacts.GetOtherComponent()) && !pHitHeli->DoesProjectileHitPropeller(impacts.GetOtherComponent())) { impacts.DisableImpact(); impacts++; continue; } } // B*1954149: Do the same for plane propellers. if(pHitEntity && pHitEntity->GetIsTypeVehicle() && ((CVehicle *)pHitEntity)->InheritsFromPlane()) { CPlane *pHitPlane = (CPlane *)pHitEntity; if(pAmmoInfo->GetIsSticky() && pHitPlane->DoesBulletHitPropellerBound(impacts.GetOtherComponent())) { impacts.DisableImpact(); impacts++; continue; } if(pHitPlane->IsPropeller(impacts.GetOtherComponent()) && !pHitPlane->DoesProjectileHitPropeller(impacts.GetOtherComponent())) { impacts.DisableImpact(); impacts++; continue; } } if(GetInfo()->GetShouldStickToPeds()) { const bool bIsPedCapsule = pOtherInst ? ( CPhysics::GetLevel()->GetInstanceTypeFlags( pOtherInst->GetLevelIndex() ) & ArchetypeFlags::GTA_PED_TYPE) > 0 : false; if(pAmmoInfo->GetIsSticky() && bIsPedCapsule) { impacts.DisableImpact(); impacts++; continue; } } if(m_iFlags.IsFlagSet(PF_Active)) { Vector3 vMyNormal; impacts.GetMyNormal( vMyNormal ); float fDotNormal = rage::Abs( DotProduct( vMyNormal, VEC3V_TO_VECTOR3( GetTransform().GetB() ) ) ); if(!m_iFlags.IsFlagSet(PF_AnyImpactDetected) && !pAmmoInfo->GetCanBounce()) { // Tag that an impact has been detected m_iFlags.SetFlag(PF_AnyImpactDetected); if(!m_bNetworkHasHitPlayer) { impacts.SetElasticity( s_fProjectileFirstHitElasticity ); } #if __ASSERT else { Assertf(NetworkInterface::IsGameInProgress(),"Only expect m_bNetworkHasHitPlayer to be used in net games"); } #endif phArchetype* myArch = GetCurrentPhysicsInst()->GetArchetype(); phBound* myBound = myArch != NULL ? myArch->GetBound() : NULL; if(myBound != NULL) { float projectileFriction = s_fProjectileFriction; const float closeProjectileFriction = s_fProjectileFriction * pAmmoInfo->GetFrictionMultiplier(); if( m_pOwner && m_pOwner->GetIsTypePed() ) { CPed* pOwnerPed = static_cast( m_pOwner.Get() ); Vector3 pedPos = VEC3V_TO_VECTOR3(pOwnerPed->GetTransform().GetPosition()); pedPos.Subtract(VEC3V_TO_VECTOR3(impacts.GetOtherPosition())); static dev_float sf_MinDistanceForFrictionIncrease = 2.0f; static dev_float sf_MaxDistanceForFrictionIncrease = 10.0f; float distanceToPed = pedPos.XYMag(); if( distanceToPed < sf_MinDistanceForFrictionIncrease ) { // B*1928201: Allow projectiles to specify a friction multiplier so they can better control how much they will slide on the ground. projectileFriction = pAmmoInfo->GetFrictionMultiplier() * s_fProjectileFriction; } else if( distanceToPed < sf_MaxDistanceForFrictionIncrease ) { distanceToPed -= sf_MinDistanceForFrictionIncrease; projectileFriction = closeProjectileFriction + ( ( s_fProjectileFirstHitFriction - closeProjectileFriction ) * ( distanceToPed / ( sf_MaxDistanceForFrictionIncrease - sf_MinDistanceForFrictionIncrease ) ) ); // B*1928201: Projectile friction multiplier is applied with strength that diminishes as the projectile moves towards the high-friction distance threshold. projectileFriction *= pAmmoInfo->GetFrictionMultiplier() * ( 1.0f - ( distanceToPed / ( sf_MaxDistanceForFrictionIncrease - sf_MinDistanceForFrictionIncrease ) ) ); } else { // Keep projectiles that are fired over long distances accurate by preventing them from sliding away from the target. projectileFriction = s_fProjectileFirstHitFriction; } } else { projectileFriction = s_fProjectileFirstHitFriction; } if( bIsFlare ) { impacts.SetFriction( s_fProjectileFlareFirstHitFriction ); } else { impacts.SetFriction( projectileFriction ); } impacts.SetMyPositionLocal(myBound->GetCGOffset()); } } else { if( bIsFlare ) { impacts.SetFriction( s_fProjectileFlareFriction ); } else { impacts.SetFriction( s_fProjectileFriction ); // If a bouncing projectile (ball), we also need to set this flag so that impact events can fire later on. if(pAmmoInfo->GetCanBounce()) { m_iFlags.SetFlag(PF_AnyImpactDetected); } } } if(m_bNetworkHasHitPlayer) { Assertf(NetworkInterface::IsGameInProgress(),"m_bNetworkHasHitPlayer only expected set in net games"); //Keep friction high - reduces distance skittering away impacts.SetFriction( s_fProjectileFirstHitFriction ); } if(fDotNormal > fDotNormalLimit || ( pHitEntity && pHitEntity->GetIsTypeObject() && static_cast< CObject* >( pHitEntity )->GetProjectilesShouldExplodeOnImpact() ) ) { m_vExplodePos = VEC3V_TO_VECTOR3( impacts.GetOtherPosition() ); m_vExplodeNormal = vMyNormal; m_pHitEntity = pHitEntity; m_pOtherInst = pOtherInst; m_iOtherComponent = impacts.GetOtherComponent(); m_iOtherMaterialId = impacts.GetOtherMaterialId(); m_iFlags.SetFlag(PF_Impacted); } if(pAmmoInfo->GetDelayUntilSettled()) { if(!m_iFlags.IsFlagSet(PF_ExplosionTriggered) && GetAge() > GetInfo()->GetExplosionTime() && m_vOldSpeed.Mag2() < 0.1f) { m_vExplodePos = VEC3V_TO_VECTOR3( impacts.GetOtherPosition() ); m_vExplodeNormal = vMyNormal; if(!pHitEntity || !pHitEntity->GetIsTypePed()) { m_pHitEntity = pHitEntity; } m_pOtherInst = pOtherInst; m_iOtherComponent = impacts.GetOtherComponent(); m_iOtherMaterialId = impacts.GetOtherMaterialId(); m_iFlags.SetFlag(PF_Impacted); m_iFlags.SetFlag(PF_ExplodeNextFrame); } } // if the projectile has impacted then its explosion position will be set to the collision position // check if we need to override this to be the trail effect position instead if (m_iFlags.IsFlagSet(PF_Impacted) && pAmmoInfo->GetExplodeAtTrailFxPos()) { CWeaponModelInfo* pModelInfo = static_cast(GetBaseModelInfo()); s32 trailBoneId = pModelInfo->GetBoneIndex(WEAPON_VFX_PROJTRAIL); Mat34V vBoneMtx; CVfxHelper::GetMatrixFromBoneIndex(vBoneMtx, this, trailBoneId); m_vExplodePos = VEC3V_TO_VECTOR3(vBoneMtx.GetCol3()); } //B*1837611: Set m_pHitEntity m_pOtherInst for sticky bombs so we can apply a small amount of damage on stick. if (GetInfo()->GetExplosionTag() == EXP_TAG_STICKYBOMB) { m_pHitEntity = pHitEntity; m_pOtherInst = pOtherInst; m_vExplodePos = VEC3V_TO_VECTOR3( impacts.GetOtherPosition() ); m_vExplodeNormal = vMyNormal; } else { // Determine whether or not we should toggle the life time flag on if(!m_iFlags.IsFlagSet(PF_UsingLifeTimeAfterImpact) && GetInfo()->GetLifeTimeAfterImpact() > 0.0f) { m_iFlags.SetFlag(PF_UsingLifeTimeAfterImpact); } } } // Process sticky collisions const float fProjectileWidth = GetBoundingBoxMax().GetZ() - GetBoundingBoxMin().GetZ(); if( !m_iFlags.IsFlagSet( PF_ProcessCollisionProbe ) && pHitEntity && pAmmoInfo->GetIsSticky() && CProjectile::ShouldStickToEntity( pHitEntity, GetOwner(), fProjectileWidth, impacts.GetOtherPosition(), impacts.GetOtherComponent(), impacts.GetOtherMaterialId(), false, GetInfo()->GetShouldStickToPeds(), m_bHitPedFriendly ) ) { // We do not actually want to impact with the object it sticks to otherwise it will apply an undesired impulse impacts.DisableImpact(); m_iFlags.SetFlag( PF_ProcessCollisionProbe ); } } impacts++; } } void CProjectile::ProcessPostPhysics() { //B*1737659 - Moved sticky test from PostSimUpdate to ProcessPostPhysics to fix projectiles passing through peds before popping back to attach position if( m_iFlags.IsFlagSet( PF_ProcessCollisionProbe ) && !m_iFlags.IsFlagSet( PF_Sticked ) ) { ProcessStickyShapeTest(); } m_matPrevious = GetMatrix(); if(m_pStickEntity) { StickToEntity(); } CheckOwner(); CPhysical::ProcessPostPhysics(); } void CProjectile::PostSimUpdate() { if(m_iFlags.IsFlagSet(PF_Impacted) && !m_iFlags.IsFlagSet(PF_DisableImpactExplosion)) { //Check if the explosion position isn't under water before exploding //Example of this is Molotov hitting shallow water and the projectile position not classed as in water. if(!GetIsInWater() && m_iFlags.IsFlagSet(PF_Active)) { ProcessInWater(true); if(!m_iFlags.IsFlagSet(PF_Active)) { m_iFlags.ClearFlag(PF_Impacted); return; } } bool bCheckForImpactDamage = true; bool bForceExplodeOnImpact = m_pHitEntity && m_pHitEntity->GetIsTypeObject() && static_cast< CObject* >( m_pHitEntity.Get() )->GetProjectilesShouldExplodeOnImpact(); if(GetInfo()->GetExplosionTag(m_pHitEntity) != EXP_TAG_DONTCARE) { // If the explosion doesn't cause damage, we might want to apply impact damage later instead const CExplosionTagData& explosionData = CExplosionInfoManager::m_ExplosionInfoManagerInstance.GetExplosionTagData(GetInfo()->GetExplosionTag(m_pHitEntity)); if (explosionData.damageAtCentre > 0.0f || explosionData.damageAtEdge > 0.0f) { bCheckForImpactDamage = false; } m_iFlags.SetFlag(PF_ForceExplosion); Explode(m_vExplodePos, m_vExplodeNormal, m_pHitEntity, true); } if(m_pHitEntity && (m_pHitEntity != m_pIgnoreDamageEntity) && !GetIsEntityPartOfTrailer(m_pHitEntity, m_pIgnoreDamageEntity) && GetInfo()->GetShouldApplyDamageOnImpact() && !m_bAppliedImpactDamage && bCheckForImpactDamage) { if(GetInfo()->GetDamage() > 0.0f) { // Setup a temp weapon CWeapon tempWeapon(m_uWeaponFiredFromHash, 1); // Setup a temp intersection from our parameters WorldProbe::CShapeTestHitPoint tempResult; tempResult.SetHitInst(m_pOtherInst->GetLevelIndex(), PHLEVEL->GetGenerationID(m_pOtherInst->GetLevelIndex())); tempResult.SetHitPosition(m_vExplodePos); tempResult.SetHitNormal(m_vExplodeNormal); tempResult.SetHitTValue(0.0f); tempResult.SetHitDepth(0.0f); tempResult.SetHitPartIndex(0); tempResult.SetHitComponent((u16)m_iOtherComponent); tempResult.SetHitMaterialId(m_iOtherMaterialId); WorldProbe::CShapeTestFixedResults<> tempResults; tempResults.Push(tempResult); // Apply impact damage CWeaponDamage::DoWeaponImpact(&tempWeapon, m_pOwner, VEC3V_TO_VECTOR3(GetTransform().GetPosition()), tempResults, GetInfo()->GetDamage(), CPedDamageCalculator::DF_IgnoreArmor); // Send a network damage event for the projectile hit if(NetworkInterface::IsGameInProgress() && !NetworkUtils::IsNetworkClone(m_pOwner) && NetworkUtils::IsNetworkClone(m_pHitEntity)) { CWeaponDamageEvent::Trigger(m_pOwner, m_pHitEntity, m_vExplodePos, m_iOtherComponent, false, m_uWeaponFiredFromHash, GetInfo()->GetDamage(), -1, -1, CPedDamageCalculator::DF_IgnoreArmor, 0, 0, 0); tempWeapon.SendFireMessage(m_pOwner, m_vExplodePos, tempResults, 1, true, GetInfo()->GetDamage(), CPedDamageCalculator::DF_IgnoreArmor); } m_bAppliedImpactDamage = true; } } if(GetInfo()->GetTrailFxRemovedOnImpact()) { // Remove active particle effects g_ptFxManager.RemovePtFxFromEntity(this); m_iFlags.SetFlag(PF_TrailInactive); } if(GetInfo()->GetShouldBeDestroyedOnImpact() || bForceExplodeOnImpact ) { m_iFlags.SetFlag(PF_ForceExplosion); TriggerExplosion(); } } //only make the sound event once and only if we detected an impact //handling events should be done at the end of physics not the beginning, which is why we check now //instead of before during PreComputeImpacts //but dont react until the frame after we hit the ped...this will prevent getting replaced by the NMBalance task if(m_iFlags.IsFlagSet(PF_AnyImpactDetected) && !m_iFlags.IsFlagSet(PF_HitPedThisFrame) && !m_iFlags.IsFlagSet(PF_MadeSoundEvent)) { m_iFlags.SetFlag(PF_MadeSoundEvent); CreateImpactEvents(); } m_iFlags.ChangeFlag(PF_HitPedLastFrame, m_iFlags.IsFlagSet(PF_HitPedThisFrame)); m_iFlags.ClearFlag(PF_HitPedThisFrame); if(!m_iFlags.IsFlagSet(PF_HitPedLastFrame)) { m_pHitPed = NULL; } if(m_iFlags.IsFlagSet(PF_Active)) { m_vOldSpeed = GetVelocity(); } } void CProjectile::SetPosition(const Vector3& vec, bool bUpdateGameWorld, bool bUpdatePhysics, bool bWarp) { CObject::SetPosition(vec, bUpdateGameWorld, bUpdatePhysics, bWarp); if (bWarp) { m_matPrevious = GetMatrix(); } } void CProjectile::SetMatrix(const Matrix34& mat, bool bUpdateGameWorld, bool bUpdatePhysics, bool bWarp) { CObject::SetMatrix(mat, bUpdateGameWorld, bUpdatePhysics, bWarp); if (bWarp) { m_matPrevious = GetMatrix(); } } void CProjectile::Fire(const Vector3& vDirection, const f32 fLifeTime, f32 fLaunchSpeedOverride, bool bAllowDamping, bool bScriptControlled, bool bCommandFireSingleBullet, bool bIsDrop, const Vector3* vTargetVelocity, bool bDisableTrail, bool bAllowToSetOwnerAsNoCollision) { m_iTimeProjectileWasFired = fwTimer::GetTimeInMilliseconds(); m_vPositionFiredFrom = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); // Set as active m_iFlags.SetFlag(PF_Active); if (bDisableTrail) { m_iFlags.SetFlag(PF_TrailInactive); } InitLifetimeValues(fLifeTime); bool bBoom = false; if(m_iFlags.IsFlagSet(PF_UsingLifeTimer) && m_fExplosionTime==0.0f) bBoom = true; // Uses collision records SetRecordCollisionHistory(true); if(m_pOwner && m_pOwner->GetIsPhysical() && !m_pOwner->GetIsTypePed()) { const Vector3 vLocalSpeed = static_cast(*m_pOwner).GetLocalSpeed(VEC3V_TO_VECTOR3(GetTransform().GetPosition()), true); // Hack to remove velocity impulse for torpedoes on the Kosatka if(MI_SUB_KOSATKA.IsValid() && m_pOwner->GetModelIndex()==MI_SUB_KOSATKA && m_uWeaponFiredFromHash == ATSTRINGHASH("VEHICLE_WEAPON_KOSATKA_TORPEDO", 0x62E2140E)) { const Vector3 vLocalSpeedXY = Vector3(vLocalSpeed.x, vLocalSpeed.y, 0.0f); Displayf("CProjectile::Fire: SetVelocity - vLocalSpeed(%.2f,%.2f,%.2f)", vLocalSpeedXY.GetX(), vLocalSpeedXY.GetY(), vLocalSpeedXY.GetZ()); SetVelocity(vLocalSpeedXY); } else { Displayf("CProjectile::Fire: SetVelocity - vLocalSpeed(%.2f,%.2f,%.2f)", vLocalSpeed.GetX(), vLocalSpeed.GetY(), vLocalSpeed.GetZ()); SetVelocity(vLocalSpeed); } } CPed* pPed = NULL; if(m_pOwner && m_pOwner->GetIsTypePed()) { pPed = static_cast(m_pOwner.Get()); } // Apply any initial impulse if(!GetAsProjectileRocket() && !bBoom) { float fLaunchSpeed = fLaunchSpeedOverride != -1.0f? fLaunchSpeedOverride : GetInfo()->GetLaunchSpeed(); Vector3 vLaunchVelocity = vDirection * fLaunchSpeed; Vector3 vReferenceFrameVelocity(0.0f,0.0f,0.0f); if (pPed) { if(GetHash() == ATSTRINGHASH("AMMO_BIRD_CRAP", 0x4298C094)) { audSoundInitParams initParams; initParams.TrackEntityPosition = true; audSoundSet soundSet; soundSet.Init(ATSTRINGHASH("PEYOTE_BIRD_POOP_SOUNDS", 0x6DCF1C4B)); if(soundSet.IsInitialised() && pPed->GetPedAudioEntity()) { pPed->GetPedAudioEntity()->CreateAndPlaySound(soundSet.Find(ATSTRINGHASH("POOP", 0x21F0FA65)), &initParams); } } //vReferenceFrameVelocity = pPed->GetVelocity(); B* 1415564 Leave ped velocity zero if they are not riding something, to ensure accurate hits on reticule if (!bIsDrop) { //Add vehicle velocity modifiers if ( pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_InVehicle) && pPed->GetMyVehicle()) //add vehicle velocity { vReferenceFrameVelocity = pPed->GetMyVehicle()->GetVelocity(); } else if (pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_OnMount) && pPed->GetMyMount()) { vReferenceFrameVelocity = pPed->GetMyMount()->GetVelocity(); } else if (pPed->GetPedResetFlag(CPED_RESET_FLAG_IncludePedReferenceVelocityWhenFiringProjectiles)) { vReferenceFrameVelocity = pPed->GetVelocity(); } else if (pPed->GetPedResetFlag(CPED_RESET_FLAG_IsParachuting)) { const CTaskParachute* pParachuteTask = static_cast(pPed->GetPedIntelligence()->FindTaskByType(CTaskTypes::TASK_PARACHUTE)); if(pParachuteTask && pParachuteTask->GetParachute()) { vReferenceFrameVelocity = pParachuteTask->GetParachute()->GetVelocity(); } } #if ENABLE_JETPACK else if (pPed->GetPedResetFlag(CPED_RESET_FLAG_IsUsingJetpack) && pPed->GetHasJetpackEquipped()) { vReferenceFrameVelocity = pPed->GetVelocity(); } #endif } } // If we're trying to hit a target add the target velocity regardless of how fast we're going, otherwise use the reference frame velocity. vLaunchVelocity += vTargetVelocity ? *vTargetVelocity : vReferenceFrameVelocity; #if __BANK const Vector3 vTargetVelocityDebug = vTargetVelocity ? *vTargetVelocity : Vector3(0.0f, 0.0f, 0.0f); Displayf("CProjectile::Fire: SetVelocity - vLaunchVelocity(%.2f,%.2f,%.2f), vTargetVelocity(%.2f,%.2f,%.2f), vReferenceFrameVelocity(%.2f,%.2f,%.2f)", vLaunchVelocity.GetX(), vLaunchVelocity.GetY(), vLaunchVelocity.GetZ(), vTargetVelocityDebug.GetX(), vTargetVelocityDebug.GetY(), vTargetVelocityDebug.GetZ(), vReferenceFrameVelocity.GetX(), vReferenceFrameVelocity.GetY(), vReferenceFrameVelocity.GetZ()); #endif // __BANK SetVelocity(vLaunchVelocity); phCollider* pCollider = GetCollider(); if(pCollider) { if (!bAllowDamping) { // Completely disable damping pCollider->SetDampingEnabled(false); } else { // If we aren't totally getting rid of damping, just set a temporary reference frame velocity // so we slowly transition into becoming damped. pCollider->SetReferenceFrameVelocity(vReferenceFrameVelocity); TUNE_GROUP_FLOAT(PROJECTILE_THROW_TUNE, REFERENCE_FRAME_DAMPING_RATE, 0.2f,0.0f,5.0f,0.1f); pCollider->SetReferenceFrameVelocityDampingRate(REFERENCE_FRAME_DAMPING_RATE); } } // make it spin clockwise Vector3 vAngDireciton = CrossProduct(vDirection, Vector3(0.0f, 0.0f, 1.0f)); float fCentroidRadius = 0.0f; Vector3 vWorldCentroid; fCentroidRadius = GetBoundCentreAndRadius(vWorldCentroid); ApplyAngImpulse(vAngDireciton * fLaunchSpeed * GetMass(), Vector3(0.0f, 0.0f, fCentroidRadius*0.8f), 0, false, IF_InitialImpulse); } else { if (pPed) { Vector3 vGroundVelocity = VEC3V_TO_VECTOR3(pPed->GetGroundVelocity()); Displayf("CProjectile::Fire: SetVelocity - vGroundVelocity(%.2f,%.2f,%.2f)", vGroundVelocity.GetX(), vGroundVelocity.GetY(), vGroundVelocity.GetZ()); SetVelocity(vGroundVelocity); } else if (m_pOwner && m_pOwner->GetIsTypeVehicle()) { CVehicle *pVehicle = NULL; pVehicle = static_cast(m_pOwner.Get()); if (pVehicle) { Vector3 vVehicleVelocity = pVehicle->GetVelocity(); // Hack to remove velocity impulse for torpedoes on the Kosatka if(MI_SUB_KOSATKA.IsValid() && pVehicle->GetModelIndex()==MI_SUB_KOSATKA && m_uWeaponFiredFromHash == ATSTRINGHASH("VEHICLE_WEAPON_KOSATKA_TORPEDO", 0x62E2140E)) { const Vector3 vVehicleVelXY = Vector3(vVehicleVelocity.x, vVehicleVelocity.y, 0.0f); Displayf("CProjectile::Fire: SetVelocity - vVehicleVelocity(%.2f,%.2f,%.2f)", vVehicleVelXY.GetX(), vVehicleVelXY.GetY(), vVehicleVelXY.GetZ()); SetVelocity(vVehicleVelXY); } else { Displayf("CProjectile::Fire: SetVelocity - vVehicleVelocity(%.2f,%.2f,%.2f)", vVehicleVelocity.GetX(), vVehicleVelocity.GetY(), vVehicleVelocity.GetZ()); SetVelocity(vVehicleVelocity); } } } } if (bAllowToSetOwnerAsNoCollision) SetNoCollision(m_pOwner, NO_COLLISION_RESET_WHEN_NO_BBOX); // Create projectile across the network if(NetworkInterface::IsGameInProgress()) { if (m_pOwner && m_pOwner->GetIsTypeVehicle()) { CVehicle *pVehicle = NULL; pVehicle = static_cast(m_pOwner.Get()); if (pVehicle) { if(MI_SUB_KOSATKA.IsValid() && (pVehicle->GetModelIndex()==MI_SUB_KOSATKA) && (m_uWeaponFiredFromHash == ATSTRINGHASH("VEHICLE_WEAPON_KOSATKA_TORPEDO", 0x62E2140E))) { GetEventScriptNetworkGroup()->Add(CEventNetworkFiredVehicleProjectile(pVehicle, pVehicle->GetDriver(), this, m_uWeaponFiredFromHash)); } } } if(!m_networkIdentifier.IsClone()) { float fSynchedLaunchSpeedOverride = fLaunchSpeedOverride; CProjectileRocket* pRocket= GetAsProjectileRocket(); if(pRocket && pRocket->GetLauncherSpeed()>0.0f) { fSynchedLaunchSpeedOverride = pRocket->GetLauncherSpeed(); } if(GetWeaponFiredFromHash() == ATSTRINGHASH("WEAPON_FLAREGUN", 0x47757124)) { SetTaskSequenceId(CProjectileManager::AllocateNewFlareSequenceId()); } else if(pRocket) { if(pPed && pPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_AIM_GUN_ON_FOOT)) { //Coordinate with TASK_AIM_GUN_ON_FOOT and sync task sequence when firing rockets B*1972374 pRocket->SetTaskSequenceId(pPed->GetPedIntelligence()->GetQueriableInterface()->GetTaskNetSequenceForType(CTaskTypes::TASK_AIM_GUN_ON_FOOT)); } } CStartProjectileEvent::Trigger(this, vDirection, bCommandFireSingleBullet, bAllowDamping, fSynchedLaunchSpeedOverride); // Fire out a script event if this is a special ped firing a special kind of projectile. Script need to pick it up if(pPed && pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_FiresDummyRockets)) { GetEventScriptNetworkGroup()->Add(CEventNetworkFiredDummyProjectile(pPed, this, m_uWeaponFiredFromHash)); } } else if(GetAsProjectileRocket()) { //Check what to do with clone rockets and our vehicle if we are a passenger CVehicle* pVehicle = CGameWorld::FindLocalPlayerVehicle(); if( pVehicle && pVehicle->IsNetworkClone() ) { //We're passenger so don't allow collision with our vehicle to destroy or detonate rocket, just turn off collision to allow //the rocket to continue on its path with its trail if comes close. //Only the owner/driver of the vehicle should decide the end point and explosion result of the projectile SetNoCollision(pVehicle, NO_COLLISION_NEEDS_RESET); } // Fire out a script event if this is a special ped firing a special kind of projectile. Script need to pick it up if(pPed && pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_FiresDummyRockets)) { GetEventScriptNetworkGroup()->Add(CEventNetworkFiredDummyProjectile(pPed, this, m_uWeaponFiredFromHash)); } } } // Cache off whether or not this projectile is script controlled SetIsScriptControlled( bScriptControlled ); if (bBoom) { //attach to ped in case it is moving B* 1088915 if(m_pOwner && m_pOwner->GetIsTypePed()) { CPed* pPed = static_cast(m_pOwner.Get()); Vector3 vAttachOffset = VEC3_ZERO; //TODO: left hand? Offset? AttachToPhysicalBasic(pPed, s16(pPed->GetBoneIndexFromBoneTag(BONETAG_R_PH_HAND)), ATTACH_STATE_BASIC|ATTACH_FLAG_DELETE_WITH_PARENT, &vAttachOffset, NULL); } m_iFlags.SetFlag(PF_ForceExplosion); TriggerExplosion(); } } void CProjectile::RestoreDamping() { if(phCollider* pCollider = GetCollider()) { pCollider->SetDampingEnabled(true); } } void CProjectile::OnAttachment() { if(m_pStickEntity && m_pStickEntity->GetIsTypeVehicle()) { CVehicle *pStickVehicle = (CVehicle *)m_pStickEntity.Get(); const void *basePtr = NULL; if(pStickVehicle->GetVehicleDamage() && pStickVehicle->GetVehicleDamage()->GetDeformation() && pStickVehicle->GetVehicleDamage()->GetDeformation()->HasDamageTexture()) { basePtr = pStickVehicle->GetVehicleDamage()->GetDeformation()->LockDamageTexture(grcsRead); //Lock the texture once for all wheels } if(basePtr) { ApplyDeformation(pStickVehicle, basePtr, true); pStickVehicle->GetVehicleDamage()->GetDeformation()->UnLockDamageTexture(); } } m_iFlags.SetFlag(PF_Sticked); // Set as active m_iFlags.SetFlag(PF_Active); if(m_pHitEntity && (m_pHitEntity != m_pIgnoreDamageEntity) && !GetIsEntityPartOfTrailer(m_pHitEntity, m_pIgnoreDamageEntity) && m_pOtherInst && !NetworkUtils::IsNetworkClone(m_pHitEntity)) { bool bOverrideStickyBombDamage = false; if (m_pHitEntity->GetIsTypePed()) { m_iFlags.SetFlag(PF_StuckToPed); bOverrideStickyBombDamage = !m_bHitPedFriendly && NetworkInterface::IsGameInProgress() && GetInfo()->GetExplosionTag() == EXP_TAG_STICKYBOMB; } if(GetInfo()->GetDamage() > 0.0f || bOverrideStickyBombDamage) { //Set weapon type to WEAPONTYPE_SMOKEGRENADE if we're applying a small amount of sticky hit damage //Can't use WEAPONTYPE_STICKYBOMB as causes explosion reaction, and can't use WEAPONTYPE_FALL as some code in CEventDamage::ComputePersonalityResponseToDamage clears the threat response if that's the damage type u32 uWeaponFiredFromHashCached = m_uWeaponFiredFromHash; if (bOverrideStickyBombDamage) { m_uWeaponFiredFromHash = WEAPONTYPE_SMOKEGRENADE; } // Setup a temp weapon CWeapon tempWeapon(m_uWeaponFiredFromHash, 1); // Setup a temp intersection from our parameters WorldProbe::CShapeTestHitPoint tempResult; tempResult.SetHitInst(m_pOtherInst->GetLevelIndex(), PHLEVEL->GetGenerationID(m_pOtherInst->GetLevelIndex())); tempResult.SetHitPosition(m_vExplodePos); tempResult.SetHitNormal(m_vExplodeNormal); tempResult.SetHitTValue(0.0f); tempResult.SetHitDepth(0.0f); tempResult.SetHitPartIndex(0); tempResult.SetHitComponent((u16)m_iOtherComponent); tempResult.SetHitMaterialId(m_iOtherMaterialId); WorldProbe::CShapeTestFixedResults<> tempResults; tempResults.Push(tempResult); CPed *pHitPed = NULL; if (bOverrideStickyBombDamage && m_pHitEntity->GetIsTypePed()) { CEntity *pEntity = m_pHitEntity; if (pEntity) { pHitPed = static_cast(pEntity); } } //Disable ragdolling if we're doing stickybomb stick damage bool bIsRagdollEnabled = pHitPed ? pHitPed->GetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromFalling) : false; bool bDisableRagdoll = pHitPed && bOverrideStickyBombDamage; if (bDisableRagdoll) pHitPed->SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromFalling, true); bool bGenerateVFX = true; if (bOverrideStickyBombDamage) { bGenerateVFX = false; } // Apply damage const float fStickyBombStickDamage = 0.1f; CWeaponDamage::DoWeaponImpact(&tempWeapon, m_pOwner, VEC3V_TO_VECTOR3(GetTransform().GetPosition()), tempResults, bOverrideStickyBombDamage ? fStickyBombStickDamage : GetInfo()->GetDamage(), CPedDamageCalculator::DF_None, false, false, NULL, NULL,false,NULL,1.f,1.f,1.f,false, bGenerateVFX); // Restore the weapon hash to what it was before we changed it to WEAPONTYPE_SMOKEGRENADE if (bOverrideStickyBombDamage) { m_uWeaponFiredFromHash = uWeaponFiredFromHashCached; } //Re-enable ragdolling if (bDisableRagdoll) pHitPed->SetPedConfigFlag(CPED_CONFIG_FLAG_DontActivateRagdollFromFalling, bIsRagdollEnabled); // send a network damage event for the projectile hit if(NetworkInterface::IsGameInProgress() && !NetworkUtils::IsNetworkClone(m_pOwner) && NetworkUtils::IsNetworkClone(m_pHitEntity)) { CWeaponDamageEvent::Trigger(m_pOwner, m_pHitEntity, m_vExplodePos, m_iOtherComponent, false, m_uWeaponFiredFromHash, 0.0f, -1, -1, CPedDamageCalculator::DF_None, 0, 0, 0); } } } } void CProjectile::Destroy() { // Make sure the projectile is added to the scene update. if (!GetIsOnSceneUpdate()) { AddToSceneUpdate(); } // Only delete projectile objects that are not already about to be deleted if(IsBaseFlagSet(fwEntity::REMOVE_FROM_WORLD)) { return; } // Deactivate m_iFlags.ClearFlag(PF_Active); // Mark to be deleted SetBaseFlag(fwEntity::REMOVE_FROM_WORLD); // Hide - as it wont get deleted this frame SetIsVisibleForModule( SETISVISIBLE_MODULE_GAMEPLAY, false, true ); // Turn off collision RemovePhysics(); DisableCollision(); const CEntity* pOwnerConst = GetOwner(); if(!m_iFlags.IsFlagSet(PF_NoStickyBombOwnership) && pOwnerConst && pOwnerConst->GetIsTypePed()) { CEntity* pOwner = const_cast(pOwnerConst); CPed* pPedOwner = static_cast(pOwner); if(m_pInfo && m_pInfo->GetIsSticky()) { if (m_pInfo->GetShouldStickToPeds()) { pPedOwner->DecrementStickToPedProjectileCount(); } else { pPedOwner->DecrementStickyCount(); } } else if (m_pInfo && m_pInfo->GetHash() == AMMOTYPE_DLC_FLAREGUN) { pPedOwner->DecrementFlareGunProjectileCount(); } } } bool CProjectile::FadeOutProjectile() { TUNE_GROUP_FLOAT(PROJECTILE_STICK_TO_PED_TUNE, FADE_TIME, 1.0f, 1.0f, 2500.0f, 1.0f); m_FadeTime += fwTimer::GetTimeStepInMilliseconds(); if (m_FadeTime < FADE_TIME) { u32 alpha = static_cast(255.0f - ((((float)m_FadeTime / FADE_TIME)) * 255.0f)); SetAlpha(alpha); } if (m_FadeTime >= FADE_TIME) { return true; } return false; } void CProjectile::TriggerExplosion(const u32 iRandomDelayMax /*= 0*/) { if(!m_iFlags.IsFlagSet(PF_ExplosionTriggered) && !m_iFlags.IsFlagSet(PF_StuckToSpectatorPedOrGhostVeh)) { physicsAssert(IsRecordingCollisionHistory()); const CCollisionRecord * pMostSignificantRecord = GetFrameCollisionHistory()->GetMostSignificantCollisionRecord(); Vector3 vDir = GetVelocity(); if(vDir.Mag2() > 0.0f) { vDir.NormalizeFast(); } else if(pMostSignificantRecord) { vDir = pMostSignificantRecord->m_MyCollisionNormal; physicsAssertf(vDir.Mag()>=0.997f && vDir.Mag()<=1.003f, "Normal: (%5.3f, %5.3f, %5.3f)", vDir.x, vDir.y, vDir.z); } else { vDir.Zero(); } if (vDir.IsZero()) { vDir = Vector3(0.0f, 0.0f, 1.0f); } const CEntity* pCollisionEntity = pMostSignificantRecord ? pMostSignificantRecord->m_pRegdCollisionEntity.Get() : NULL; if (!pCollisionEntity) pCollisionEntity = m_pHitEntity; if(pCollisionEntity != NULL) { // If owner entity is a vehicle, attribute explosion stat to ped with vehicle weapon equipped const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo(m_uWeaponFiredFromHash); if (m_pOwner && m_pOwner->GetIsTypeVehicle() && pWeaponInfo && pWeaponInfo->GetIsVehicleWeapon()) { const CVehicle* pVehicle = static_cast(m_pOwner.Get()); const int iNumSeats = pVehicle->GetSeatManager()->GetMaxSeats(); for (int iSeat = 0; iSeat < iNumSeats; ++iSeat) { if (pVehicle->GetSeatHasWeaponsOrTurrets(iSeat)) { CPed* pPed = pVehicle->GetSeatManager()->GetPedInSeat(iSeat); if (pPed) { const CVehicleWeapon* pVehWeapon = pPed->GetWeaponManager()->GetEquippedVehicleWeapon(); if (pVehWeapon && pVehWeapon->GetHash() == m_uWeaponFiredFromHash) { CStatsMgr::RegisterExplosionHitAnything(pPed, m_uWeaponFiredFromHash); break; } } } } } else { CStatsMgr::RegisterExplosionHitAnything(m_pOwner, m_uWeaponFiredFromHash); } } m_iFlags.SetFlag(PF_ExplosionTriggered); // B*2232729: Handle staggered explosions in projectile code instead of passing through activation timer to explosion code. if (iRandomDelayMax != 0) { m_vDir = vDir; m_pCollisionEntity = pMostSignificantRecord ? pMostSignificantRecord->m_pRegdCollisionEntity.Get() : NULL; m_uExplodeTime = fwTimer::GetTimeInMilliseconds() + (iRandomDelayMax + fwRandom::GetRandomNumberInRange(0,CTaskAimAndThrowProjectile::GetTunables().m_iMaxRandomExplosionTime)); m_iFlags.SetFlag(PF_UsingExplodeTimer); } else { Explode(VEC3V_TO_VECTOR3(GetTransform().GetPosition()), vDir, pCollisionEntity, pCollisionEntity ? true : false, iRandomDelayMax); } } else if(NetworkInterface::IsGameInProgress() && m_networkIdentifier.IsValid() && m_networkIdentifier.IsClone()) { if (GetInfo()->GetHash() != AMMOTYPE_DLC_FLAREGUN) { Displayf("CProjectile::TriggerExplosion: Destroy(). Reason: GetInfo()->GetHash() != AMMOTYPE_DLC_FLAREGUN."); Destroy(); } } } #if __BANK void CProjectile::RenderDebug() const { #if DEBUG_DRAW // Render the axis of the projectile static bank_float AXIS_LENGTH = 0.2f; ScalarV AXIS_LENGTHV = ScalarVFromF32(AXIS_LENGTH); const Vec3V vThisPosition = GetTransform().GetPosition(); grcDebugDraw::Line(vThisPosition, vThisPosition + GetTransform().GetA() * AXIS_LENGTHV, Color_red, Color_red); grcDebugDraw::Line(vThisPosition, vThisPosition + GetTransform().GetB() * AXIS_LENGTHV, Color_green, Color_green); grcDebugDraw::Line(vThisPosition, vThisPosition + GetTransform().GetC() * AXIS_LENGTHV, Color_blue, Color_blue); if(GetCurrentPhysicsInst()) { if(!fwTimer::IsGamePaused()) { // Render the path of the projectile CWeaponDebug::ms_debugStore.AddLine(vThisPosition, m_matPrevious.GetCol3(), Color_green, 5000); } // Render WhizzBy events processing static dev_bool s_bDebugRenderWhizzByEvents = false; if( s_bDebugRenderWhizzByEvents && m_bWhizzByEventCheckDebugDrawPending ) { grcDebugDraw::Capsule(VECTOR3_TO_VEC3V(m_vLastWhizzByDebugHeadPosition), VECTOR3_TO_VEC3V(m_vLastWhizzByDebugTailPosition), s_fWhizzByEventRadius, Color_yellow, false, 30); } } #endif // DEBUG_DRAW } #endif // __BANK bool CProjectile::CalculateTimeUntilExplosion(float& fTime) const { //Set the time. fTime = FLT_MAX; //Check if we are active. if(m_iFlags.IsFlagSet(PF_Active)) { //Check if we are using a life timer. if(m_iFlags.IsFlagSet(PF_UsingLifeTimer)) { //Check if the explosion has not triggered. if(!m_iFlags.IsFlagSet(PF_ExplosionTriggered)) { //Set the time. fTime = m_fExplosionTime; return true; } } } return false; } void CProjectile::Explode(const Vector3& vPosition, const Vector3& vNormal, const CEntity* pHitEntity, bool bHasCollided, const u32 iRandomDelayMax) { // Projectile should be destroyed so don't explode. This can happen if owner vehicle was removed in this frame if(CheckOwner()) { return; } #if __BANK weaponDebugf1("CProjectile::Explode() triggered for projectile: %d:%d of owner: %s", m_networkIdentifier.GetPlayerOwner(), m_networkIdentifier.GetFXId(), m_pOwner?m_pOwner->GetLogName():"No owner"); TUNE_GROUP_BOOL(PROJECTILES, bTraceExplodeCalls, false); if (bTraceExplodeCalls) { Displayf("CProjectile::Explode(): vPosition - <%.2f,%.2f,%.2f>, pHitEntity - %s[%p], bHasCollided - %s, iRandomDelayMax - %i", vPosition.GetX(), vPosition.GetY(), vPosition.GetZ(), pHitEntity ? (pHitEntity->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast(pHitEntity)) : pHitEntity->GetModelName()) : "null", pHitEntity, AILogging::GetBooleanAsString(bHasCollided), iRandomDelayMax); sysStack::PrintStackTrace(); } #endif // Ensure that we generate whizz by events for the last bit of distance travelled before impact. const bool bForceUpdate = true; ProcessWhizzByEvents(bForceUpdate); bool bDetonatingOtherPlayersExplosive = false; RestoreDamping(); // Clone projectiles do not explode (the machine which generated the projectile will also generate the explosion) if(NetworkInterface::IsGameInProgress() && m_networkIdentifier.IsClone()) { bDetonatingOtherPlayersExplosive = GetInfo() && GetInfo()->GetIsSticky(); // trying to detonate another player sticky bomb if( pHitEntity && GetAsProjectileRocket() ) { if( pHitEntity->GetIsTypePed() ) { const CPed* pPed = static_cast(pHitEntity); if (pPed->IsLocalPlayer()) { bDetonatingOtherPlayersExplosive = true; //a rocket has hit us so make sure we take control of the explosion } } else if( pHitEntity->GetIsTypeVehicle() ) { const CVehicle* pVehicle = static_cast< const CVehicle* >( pHitEntity ); if( !pVehicle->IsNetworkClone() && pVehicle->ContainsLocalPlayer() && !pVehicle->InheritsFromBoat()) { bDetonatingOtherPlayersExplosive = true; } } } if(!bDetonatingOtherPlayersExplosive) { if(GetAsProjectileRocket()!=NULL) { Displayf("CProjectile::Explode: Destroy(). Reason: !bDetonatingOtherPlayersExplosive && GetAsProjectileRocket()!=NULL."); Destroy(); } return; } } if(!m_iFlags.IsFlagSet(PF_Exploded)) { eExplosionTag tag = GetInfo()->GetExplosionTag(pHitEntity); // Override explosion tag and explosion normal if the projectile has been exploded by air defences. Vector3 vModifiedNormal = vNormal; if (m_bUseAirDefenceExplosion) { tag = EXP_TAG_AIR_DEFENCE; vModifiedNormal = m_vAirDefenceFireDirection; } if(tag != EXP_TAG_DONTCARE) { if(m_damageType != DAMAGE_TYPE_FIRE || !GetIsInWater()) { if (tag==EXP_TAG_TORPEDO && GetIsInWater()) { tag = EXP_TAG_TORPEDO_UNDERWATER; } bool bIsExplodingInAir = GetIsExplodingInAir(bHasCollided, pHitEntity); CExplosionManager::CExplosionArgs explosionArgs(tag, vPosition); explosionArgs.m_pEntExplosionOwner = m_pOwner; explosionArgs.m_pEntIgnoreDamage = m_pIgnoreDamageEntity; explosionArgs.m_bInAir = bIsExplodingInAir; explosionArgs.m_vDirection = vModifiedNormal; explosionArgs.m_originalExplosionTag = GetInfo()->GetExplosionTag(); explosionArgs.m_weaponHash = m_uWeaponFiredFromHash; explosionArgs.m_bDetonatingOtherPlayersExplosive = bDetonatingOtherPlayersExplosive; explosionArgs.m_interiorLocation = GetInteriorLocation(); if(iRandomDelayMax != 0) { explosionArgs.m_activationDelay = iRandomDelayMax + fwRandom::GetRandomNumberInRange(0,CTaskAimAndThrowProjectile::GetTunables().m_iMaxRandomExplosionTime); } // Check to see if the projectile was attached within the rear door space of the vehicle CEntity* pAttachedEntity = (CEntity*)GetAttachParentForced(); if( pAttachedEntity ) { explosionArgs.m_pAttachEntity = (pAttachedEntity && pAttachedEntity->GetDrawable()) ? pAttachedEntity : NULL; if( pAttachedEntity->GetIsTypeVehicle() ) { if(NetworkInterface::IsGameInProgress()) { //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; } CVehicle* pVehicle = (CVehicle*)pAttachedEntity; explosionArgs.m_bAttachedToVehicle = true; // Check for rear opening doors if( pVehicle->GetStatus() != STATUS_WRECKED ) pVehicle->TestExplosionPosAndBlowRearDoorsopen( vPosition ); } } else explosionArgs.m_pAttachEntity = (pHitEntity && pHitEntity->GetDrawable()) ? const_cast(pHitEntity) : NULL; if( NetworkInterface::IsGameInProgress() && explosionArgs.m_pAttachEntity && NetworkUtils::GetObjectIDFromGameObject(explosionArgs.m_pAttachEntity.Get())==NETWORK_INVALID_OBJECT_ID ) { explosionArgs.m_pAttachEntity = NULL; } //B*1752582: Smoke grenade fix //If projectile isn't flagged to be fixed after explosion and not sticky, and hit entity is dynamic, then attach the explosion to the projectile //Or if hit entity is null, attach explosion to the projectile //bool bAttachToProjecitle = GetInfo()->GetFixedAfterExplosion() && !GetInfo()->GetIsSticky() && pHitEntity && pHitEntity->GetIsDynamic(); bool bAttachToProjectile = GetInfo()->GetFixedAfterExplosion() && !GetInfo()->GetIsSticky() && explosionArgs.m_pAttachEntity && explosionArgs.m_pAttachEntity->GetIsDynamic(); if ((bAttachToProjectile || !explosionArgs.m_pAttachEntity) && tag == EXP_TAG_SMOKEGRENADE) { explosionArgs.m_pAttachEntity = static_cast(this); } if( CExplosionManager::AddExplosion(explosionArgs, this, m_iFlags.IsFlagSet(PF_ForceExplosion))) { // Exploded m_iFlags.SetFlag(PF_Exploded); m_iFlags.ClearFlag(PF_ExplosionTriggered); //B*1752582: Smoke grenade fix if(!bAttachToProjectile) { //Set fixed physics if we have a valid hit entity if (pHitEntity) { CObject::SetFixedPhysics(true); } //Stick to entity if we have valid attachment entity (which isn't ourselves) if(explosionArgs.m_pAttachEntity && explosionArgs.m_pAttachEntity != static_cast(this)) { StickToEntity(explosionArgs.m_pAttachEntity, GetTransform().GetPosition(), GetTransform().GetUp(), 0, 0); } } if(GetInfo()->GetAddSmokeOnExplosion()) { CPortalTracker* pPortalTracker = 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, VEC3V_TO_VECTOR3(GetTransform().GetPosition()), true); } } } // Additional radius explosions of a different type if (GetInfo()->GetIsCluster()) { TUNE_GROUP_BOOL(CLUSTER_BOMBS, bRenderDebug, false); explosionArgs.m_activationDelay += (u32)(GetInfo()->GetClusterInitialDelay() * 1000); explosionArgs.m_explosionTag = GetInfo()->GetClusterExplosionTag(); Vector3 newExplosionPos; float minRadius = GetInfo()->GetClusterMinRadius(); float maxRadius = GetInfo()->GetClusterMaxRadius(); int uExplosionCount = (int)(GetInfo()->GetClusterExplosionCount()); SemiShuffledSequence shuffledSegment(uExplosionCount); #if __BANK if (bRenderDebug) { grcDebugDraw::Circle(vPosition, minRadius, Color_magenta, Vector3(1.f,0.f,0.f), Vector3(0.f,1.f,0.f), false, false, 100); grcDebugDraw::Circle(vPosition, maxRadius, Color_magenta, Vector3(1.f,0.f,0.f), Vector3(0.f,1.f,0.f), false, false, 100); for (int i = 0; i < uExplosionCount; i++) { float fDebugAngle = i * (TWO_PI / (float)uExplosionCount); Vector3 vDebugStart(vPosition.x + (minRadius * rage::Sinf(fDebugAngle)), vPosition.y + (minRadius * rage::Cosf(fDebugAngle)), vPosition.z); Vector3 vDebugEnd(vPosition.x + (maxRadius * rage::Sinf(fDebugAngle)), vPosition.y + (maxRadius * rage::Cosf(fDebugAngle)), vPosition.z); grcDebugDraw::Line(vDebugStart, vDebugEnd, Color_magenta, Color_magenta, 100); } } #endif // __BANK for (int i = 0; i < uExplosionCount; i++) { // Pick a random shuffled segment so that secondary explosions always go in different directions int iSegment = shuffledSegment.GetElement(i); float fSegmentAngle = TWO_PI / (float)uExplosionCount; // Try several times, as the explosion may be rejected (most common reason being the random position was too close to an existing explosion) const int randomAttempts = 8; for (int j = 0; j < randomAttempts; j++) { // Random angle from our segment shuffler float angle = fwRandom::GetRandomNumberInRange(fSegmentAngle * iSegment, fSegmentAngle * (iSegment+1)); // Evenly distributed random radius within a ring float radius = 2 / (maxRadius * maxRadius - minRadius * minRadius); radius = Sqrtf(2 * fwRandom::GetRandomNumberInRange(0.0f, 1.0f) / radius + minRadius * minRadius); // Offset position and adjust for ground height newExplosionPos.x = vPosition.x + (radius * rage::Sinf(angle)); newExplosionPos.y = vPosition.y + (radius * rage::Cosf(angle)); newExplosionPos.z = WorldProbe::FindGroundZFor3DCoord(TOP_SURFACE, newExplosionPos.x, newExplosionPos.y, vPosition.z + 3.0f); explosionArgs.m_explosionPosition = newExplosionPos; // Attempt to create explosion at new position if (CExplosionManager::AddExplosion(explosionArgs, this, m_iFlags.IsFlagSet(PF_ForceExplosion))) { #if __BANK if (bRenderDebug) { grcDebugDraw::Sphere(newExplosionPos, 0.125f, Color_green, true, 100); grcDebugDraw::Line(newExplosionPos, vPosition, Color_green, Color_green, 100); } #endif // __BANK explosionArgs.m_activationDelay += (u32)(GetInfo()->GetClusterInbetweenDelay() * 1000); break; } } } } // Specify when we should destroy the ourselves m_fLifeTimeAfterExplosion = GetInfo()->GetLifeTimeAfterExplosion(); //B*1815582: Increase smoke grenade duration in MP if (tag == EXP_TAG_SMOKEGRENADE && NetworkInterface::IsGameInProgress()) { m_fLifeTimeAfterExplosion *= 2.0f; } //Scale explosion life time by weapon effect modifier if (auto pWeaponInfo = CWeaponInfoManager::GetInfo(m_uWeaponFiredFromHash)) { m_fLifeTimeAfterExplosion *= pWeaponInfo->GetEffectDurationModifier(); } if (m_fLifeTimeAfterExplosion <= 0.0f ) { if (m_pInfo->GetIsProximityRepeatedDetonation() && (!m_iFlags.IsFlagSet(PF_UsingLifeTimer) || m_fExplosionTime > 0.0f)) { m_iFlags.ClearFlag(PF_Exploded); m_bProximityMineActive = false; m_bProximityMineTriggered = false; m_bProximityMineTriggeredByVehicle = false; m_bProximityMineRepeatingDetonation = true; m_fProximityMineStuckTime = 0; } else if(explosionArgs.m_activationDelay > 0) { m_iFlags.SetFlag(PF_UsingDestroyTimer); m_uDestroyTime = fwTimer::GetTimeInMilliseconds() + explosionArgs.m_activationDelay + 1; } else { Displayf("CProjectile::Explode: Destroy(). Reason: explosionArgs.m_activationDelay <= 0."); Destroy(); } } } else { //Make sure the projectile explodes next frame m_iFlags.SetFlag(PF_ExplodeNextFrame); m_iFlags.ClearFlag(PF_ExplosionTriggered); } } #if __BANK CProjectileManager::SetLastExplosionPoint(vPosition, vNormal); #endif // __BANK } } } void CProjectile::ProcessHomingAttractor() { TUNE_GROUP_FLOAT(HOMING_ATTRACTOR, ATTRACTOR_MIN_DIST_TO_TARGET, 50.0f, 0.01f, 500.0f, 0.01f); TUNE_GROUP_FLOAT(HOMING_ATTRACTOR, ATTRACTOR_MAX_DIST_FROM_ATTRACTOR, 100.0f, 0.01f, 500.0f, 0.01f); TUNE_GROUP_INT(HOMING_ATTRACTOR, iTimeBeweenProcessingMS, 150, 0, 5000, 100); TUNE_GROUP_INT(HOMING_ATTRACTOR, iTimeToProcessAfterBeingFiredMS, 1000, 0, 30000, 500); // Allow flare projectiles to attract homing projectiles const CAmmoProjectileInfo* pAmmoInfo = GetInfo(); if (pAmmoInfo && pAmmoInfo->GetIsHomingAttractor()) { CPed* pOwnerPed = NULL; if(m_pOwner && m_pOwner->GetIsTypePed()) { pOwnerPed = static_cast(m_pOwner.Get()); } CVehicle* pOwningVehicle = NULL; if(m_pOwner && m_pOwner->GetIsTypeVehicle()) { pOwningVehicle = static_cast(m_pOwner.Get()); } if ( (pOwnerPed && !pOwnerPed->IsNetworkClone()) || (pOwningVehicle && !pOwningVehicle->IsNetworkClone()) ) { if (m_iTimeProjectileWasFired == 0) return; // Stop redirecting missiles after certain amount of time from being fired; u32 uTimeSinceFired = CTimeHelpers::GetTimeSince(m_iTimeProjectileWasFired); if (uTimeSinceFired >= iTimeToProcessAfterBeingFiredMS) return; u32 uTimeSinceLastUpdate = CTimeHelpers::GetTimeSince(m_iLastTimeProcessedHomingAttractor); if (m_iLastTimeProcessedHomingAttractor == 0 || uTimeSinceLastUpdate >= iTimeBeweenProcessingMS) { m_iLastTimeProcessedHomingAttractor = fwTimer::GetTimeInMilliseconds(); const CEntity* pOwningEntity = m_pOwner.Get(); if (pOwningEntity) { // Get a list of projectile rockets targeting us within a range, and switch the targets atArray projectileArray; CProjectileManager::GetProjectilesToRedirect(pOwningEntity, GetTransform().GetPosition(), projectileArray, ATTRACTOR_MIN_DIST_TO_TARGET, ATTRACTOR_MAX_DIST_FROM_ATTRACTOR); for (int i = 0; i < projectileArray.GetCount(); i++) { projectileArray[i]->SetTarget(this); projectileArray[i]->SetIsRedirected(true); if(NetworkInterface::IsGameInProgress()) { CUpdateProjectileTargetEntity::Trigger(projectileArray[i], GetTaskSequenceId()); } } } } } } } void CProjectile::ProcessInWater(const bool bUseExplosionPos) { // Cache the position Vector3 vPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); //Use the explosion position if requested if(bUseExplosionPos) { vPos = m_vExplodePos; } // Test for water splashes if(m_Buoyancy.GetWaterLevelIncludingRivers(vPos, &m_fWaterLevel, false, POOL_DEPTH, REJECTIONABOVEWATER, NULL, this)) { if(m_fWaterLevel > vPos.z) { // The projectile is underwater SetIsInWater( true ); // B*2153566: Clamp the projectile explosion time to 3.0 seconds once we've hit water (unless we're a torpedo). if(!GetInfo()->GetShouldThrustUnderwater()) { m_fExplosionTime = rage::Clamp(m_fExplosionTime, 0.0f, 3.0f); } // Check if the projectile has just gone underwater if(!m_nPhysicalFlags.bWasInWater) { m_iFlags.SetFlag(PRF_TriggerSplash); // Set timer and flag for any projectiles of DAMAGE_TYPE_FIRE/DAMAGE_TYPE_SMOKE if they are underwater to be destroyed. if(m_damageType == DAMAGE_TYPE_FIRE || m_damageType == DAMAGE_TYPE_SMOKE) { // Make sure they won't explode. m_iFlags.ClearFlag(PF_Active); if(!m_iFlags.IsFlagSet(PF_UsingDestroyTimer)) { m_iFlags.SetFlag(PF_UsingDestroyTimer); m_uDestroyTime = fwTimer::GetTimeInMilliseconds() + 2000; } } } } else { // The projectile is not underwater SetIsInWater( false ); // Check if the projectile has just come out of water if(m_nPhysicalFlags.bWasInWater) { m_iFlags.SetFlag(PRF_TriggerSplash); } } } // make the trail inactive if in water if(GetIsInWater() && GetInfo()->GetTrailFxInactiveOnceWet()) { m_iFlags.SetFlag(PF_TrailInactive); } // Determine whether or not we should toggle the life time flag on due to hitting water. const CAmmoProjectileInfo* pAmmoInfo = GetInfo(); if(GetIsInWater() && !pAmmoInfo->GetDelayUntilSettled() && pAmmoInfo->GetShouldProcessImpacts()) { if(!m_iFlags.IsFlagSet(PF_UsingLifeTimeAfterImpact) && GetLifeTimeAfterImpact() > 0.0f) { m_iFlags.SetFlag(PF_UsingLifeTimeAfterImpact); } } } void CProjectile::ProcessEffects() { CBaseModelInfo* pBaseModelInfo = GetBaseModelInfo(); if (weaponVerifyf(pBaseModelInfo && pBaseModelInfo->GetHasLoaded(), "trying to update vfx on a projectile (%s) whose base model info isn't loaded - any vfx dependencies may also not be loaded. Active: %s; Removed from world: %s; Visible: %s; Location: %f, %f, %f", pBaseModelInfo->GetModelName(), m_iFlags.IsFlagSet(PF_Active) ? "T" : "F", IsBaseFlagSet(fwEntity::REMOVE_FROM_WORLD) ? "T" : "F", IsBaseFlagSet( fwEntity::IS_VISIBLE ) ? "T" : "F", GetTransform().GetPosition().GetXf(), GetTransform().GetPosition().GetYf(), GetTransform().GetPosition().GetZf())) { if(m_iFlags.IsFlagSet(PF_Active)) { // calc the trail evolution value m_trailEvo = 1.0f; if(m_iFlags.IsFlagSet(PF_UsingLifeTimer)) { if(m_fLifeTime < GetInfo()->GetTrailFxFadeOutTime()) { m_trailEvo = CVfxHelper::GetInterpValue(m_fLifeTime, 0.0f, GetInfo()->GetTrailFxFadeOutTime()); } else if(m_fLifeTime > GetInfo()->GetLifeTime() - GetInfo()->GetTrailFxFadeInTime()) { m_trailEvo = CVfxHelper::GetInterpValue(m_fLifeTime, GetInfo()->GetLifeTime(), GetInfo()->GetLifeTime() - GetInfo()->GetTrailFxFadeInTime()); } } // do any trail effect if(!m_iFlags.IsFlagSet(PF_TrailInactive) && m_trailEvo>0.0f) { g_vfxProjectile.UpdatePtFxProjTrail(this, m_trailEvo, m_weaponTintIndex); } // probe downward looking for ground to play disturbance effects if (GetInfo()->GetDoesGroundDisturbanceFx()) { Vector3 startPos = VEC3V_TO_VECTOR3(this->GetTransform().GetPosition()); WorldProbe::CShapeTestFixedResults<> probeResult; WorldProbe::CShapeTestProbeDesc probeDesc; probeDesc.SetStartAndEnd(startPos, startPos-Vector3(0.0f, 0.0f, GetInfo()->GetDisturbFxProbeDist())); probeDesc.SetResultsStructure(&probeResult); probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES); if(WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc)) { // Play any weapon impact effects. g_vfxProjectile.UpdatePtFxProjGround(this, probeResult[0]); } } // Do we need a glow ? if( !s_grenadeAmbientModMPOnly || NetworkInterface::IsGameInProgress() ) { const eExplosionTag expTag = GetInfo()->GetExplosionTag(); if( expTag == EXP_TAG_GRENADE) { SetUseLightOverride(true); } } g_vfxProjectile.ProcessPtFxProjProximity(this, m_bProximityMineActive); } else { // do any fuse effect if(!m_iFlags.IsFlagSet(PF_TrailInactive)) { g_vfxProjectile.ProcessPtFxProjFuse(this, false); if(m_iFlags.IsFlagSet(PF_Priming)) { if(!GetIsInWater()) { g_vfxProjectile.ProcessPtFxProjFuse(this, true); } } } } // do any splash effects - going in and out of the water surface // this is not in any active/inactive section on purpose // - some projectiles (e.g. molotovs) get made inactive when they hit water but we still want the splash to happen if(m_iFlags.IsFlagSet(PRF_TriggerSplash)) { Vector3 vSplashPos(VEC3V_TO_VECTOR3(GetTransform().GetPosition())); vSplashPos.z = m_fWaterLevel; // Set up the effects info structure VfxCollInfo_s vfxCollInfo; vfxCollInfo.regdEnt = NULL; vfxCollInfo.vPositionWld = RCC_VEC3V(vSplashPos); vfxCollInfo.vNormalWld = RCC_VEC3V(ZAXIS); vfxCollInfo.vDirectionWld = RCC_VEC3V(ZAXIS); vfxCollInfo.materialId = 0; vfxCollInfo.roomId = 0; vfxCollInfo.componentId = 0; vfxCollInfo.weaponGroup = m_effectGroup; vfxCollInfo.force = VEHICLEGLASSFORCE_NOT_USED; vfxCollInfo.isBloody = false; vfxCollInfo.isWater = true; vfxCollInfo.isExitFx = false; vfxCollInfo.noPtFx = false; vfxCollInfo.noPedDamage = false; vfxCollInfo.noDecal = false; vfxCollInfo.isSnowball = false; vfxCollInfo.isFMJAmmo = false; // Play any weapon impact effects g_vfxWeapon.DoWeaponImpactVfx(vfxCollInfo, m_damageType, m_pOwner); } } } void CProjectile::ProcessAudio() { if(m_iFlags.IsFlagSet(PRF_TriggerSplash)) { Vector3 vSplashPos(VEC3V_TO_VECTOR3(GetTransform().GetPosition())); vSplashPos.z = m_fWaterLevel; audSoundInitParams initParams; initParams.Position = vSplashPos; g_WeaponAudioEntity.CreateAndPlaySound(g_WeaponAudioEntity.FindSound(ATSTRINGHASH("ProjectileSplash", 0x7B7C6F7F)), &initParams); } if(m_iFlags.IsFlagSet(PRF_TriggerBeep)) { audSoundInitParams initParams; initParams.Position = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); if (m_iFlags.IsFlagSet(PF_UsingLifeTimer) && m_pInfo->GetLightSpeedsUp()) { TUNE_GROUP_FLOAT(EXPLOSIVE_TUNE, PITCH_START_PHASE, 0.5f, 0.0f, 1.0f, 0.01f); TUNE_GROUP_FLOAT(EXPLOSIVE_TUNE, PITCH_END_MULT, 1.2f, 1.0f, 5.0f, 0.01f); float fPhase = 1 - (m_fLifeTime / m_pInfo->GetLifeTime()); if (fPhase > PITCH_START_PHASE) { float fDelta = (fPhase - PITCH_START_PHASE) / (1 - PITCH_START_PHASE); float fMult = 1.0f + ((PITCH_END_MULT - 1.0f) * fDelta); initParams.Pitch = (s16)((fMult - 1) * 1200); } } if(m_pInfo->GetIsProximityDetonation()) { audSoundSet proxSet; u32 proxSetName = GetMineSoundsetHash(GetWeaponFiredFromHash()); proxSet.Init(proxSetName); if(proxSet.IsInitialised() && !m_bProximityMineTriggered) { g_WeaponAudioEntity.CreateAndPlaySound(proxSet.Find(ATSTRINGHASH("ambient", 0xC06BF8AB)), &initParams); } } else { g_WeaponAudioEntity.CreateAndPlaySound(g_WeaponAudioEntity.FindSound(ATSTRINGHASH("sticky_bomb_beep", 0xB66B909E)), &initParams); } // Have to manually reset the flag as it gets set in PostPreRender, before the reset flags are automatically cleared m_iFlags.ClearFlag(PRF_TriggerBeep); } } u32 CProjectile::GetMineSoundsetHash(const u32 weaponNameHash) { u32 soundsetHash = 0u; // From weapon_enums.sch switch (weaponNameHash) { case 0x59EAE9A4: // VEHICLE_WEAPON_MINE soundsetHash = ATSTRINGHASH("gr_vehicle_proxmine_sounds", 0xBCA4F642); break; case 0x3C09584E: // VEHICLE_WEAPON_MINE_KINETIC case 0x69E10D60: // VEHICLE_WEAPON_MINE_EMP case 0xD96DA06C: // VEHICLE_WEAPON_MINE_SPIKE case 0x56FACAC7: // VEHICLE_WEAPON_MINE_SLICK case 0xF4418BA0: // VEHICLE_WEAPON_MINE_TAR soundsetHash = ATSTRINGHASH("DLC_AW_Vehicle_Mine_Sounds", 0x291A73D2); break; default: soundsetHash = ATSTRINGHASH("SPL_PROXMINE_SOUNDS", 0xAD9FD3AA); break; } return soundsetHash; } bool CProjectile::ShouldStickToEntity( const CEntity* pHitEntity, const CEntity* pOwner, const float fProjectileWidth, Vec3V_In vStickPos, s32 iStickComponent, phMaterialMgr::Id iStickMaterialId, bool bIgnoreWheelSphereExclusion, bool bShouldStickToPeds, bool bIsHitEntityFriendly ) { if( PGTAMATERIALMGR->UnpackMtlId( iStickMaterialId ) == PGTAMATERIALMGR->g_idCarVoid ) return false; spdSphere projectileSphere( vStickPos, ScalarV( fProjectileWidth ) ); Assert( pHitEntity ); //B*1837611: MP Only: Sticky bombs attach to non-friendly peds backs bool bShouldStickToPedsBack = false; if (pHitEntity->GetIsTypePed() && !bShouldStickToPeds && !bIsHitEntityFriendly && NetworkInterface::IsGameInProgress()) { const CPed *pHitPed = static_cast(pHitEntity); // In vehicle: only stick to peds on bike/quadbike/jetski. bool bInValidVehicle = true; if (pHitPed->GetIsInVehicle()) { const CVehicle *pVehicle = static_cast(pHitPed->GetVehiclePedInside()); if (pVehicle) { bInValidVehicle = pVehicle->InheritsFromBike() || pVehicle->InheritsFromQuadBike() || pVehicle->GetIsJetSki() || pVehicle->InheritsFromAmphibiousAutomobile(); } } if (pHitPed && !pHitPed->IsDead() && bInValidVehicle) { s32 iHitBoneTag = pHitPed->GetBoneTagFromRagdollComponent(iStickComponent); if (iHitBoneTag == BONETAG_SPINE0 || iHitBoneTag == BONETAG_SPINE1 || iHitBoneTag == BONETAG_SPINE2 || iHitBoneTag == BONETAG_SPINE3) { // Ensure that the bomb is behind the plane along the peds back (pelvis->head) before sticking. Vector3 vPelvisPosition = VEC3_ZERO; Vector3 vHeadPosition = VEC3_ZERO; pHitPed->GetBonePosition(vPelvisPosition, BONETAG_PELVIS); pHitPed->GetBonePosition(vHeadPosition, BONETAG_HEAD); Vector3 vPlane = vHeadPosition - vPelvisPosition; Vector3 vNormal = VEC3_ZERO; Vector3 vPedRight = VEC3V_TO_VECTOR3(pHitPed->GetTransform().GetA()); vNormal.Cross(vPlane, vPedRight); vNormal.Normalize(); Vector3 vStickPosVector3 = VEC3V_TO_VECTOR3(vStickPos); Vector3 vHalfPlane = vPlane; vHalfPlane.Scale(0.5f); Vector3 vPointOnPlane = vPelvisPosition + vHalfPlane; Vector3 vPlaneToBomb = vStickPosVector3 - vPointOnPlane; float fDot = vPlaneToBomb.Dot(vNormal); bShouldStickToPedsBack = (fDot < 0.0f); } } } if(pOwner && NetworkInterface::IsDamageDisabledInMP(*pHitEntity, *pOwner)) { return false; } if( pHitEntity->GetIsTypePed() && !bShouldStickToPeds && !bShouldStickToPedsBack) { return false; } else if( pHitEntity->GetIsTypeObject() ) { const CObject* pObject = static_cast(pHitEntity); // No props if( pObject->m_nObjectFlags.bAmbientProp || pObject->m_nObjectFlags.bPedProp ) return false; // No pickups if( pObject->m_nObjectFlags.bIsPickUp ) return false; // No invisible cardboard boxes attached to vehicles. B*1638542... if( IsEntityInvisibleCardboardBoxAttachedToVehicle(*pObject) ) return false; } else if( pHitEntity->GetIsTypeVehicle() ) { // Do not allow sticks to bikes or bicycles const CVehicle* pHitVehicle = static_cast( pHitEntity ); if( pHitVehicle->GetVehicleType() == VEHICLE_TYPE_BICYCLE ) return false; // Walk through each wheel and determine if we are inside the wheel bounds // Analyze stick position against each wheel as we do not want sticky bombs on wheels const CWheel* pWheel = NULL; int nNumWheels = pHitVehicle->GetNumWheels(); for( int i = 0; i < nNumWheels; i++ ) { // If we strike a wheel component we need to return immediately as we do not allow it pWheel = pHitVehicle->GetWheel(i); for( int j = 0; j < MAX_WHEEL_BOUNDS_PER_WHEEL; j++ ) { if( pWheel->GetFragChild(j) == iStickComponent ) { return false; } } if( !bIgnoreWheelSphereExclusion ) { // There are cases where the wheel in clipping with the chassis and therefore we need to run this check as well Vector3 vWheelPos; float fWheelRadius = pWheel->GetWheelPosAndRadius( vWheelPos ); spdSphere wheelSphere( VECTOR3_TO_VEC3V( vWheelPos ), ScalarV( fWheelRadius ) ); if( projectileSphere.IntersectsSphere( wheelSphere ) ) { return false; } } } } if ( !bShouldStickToPeds ) { // Do not stick if this is something that is attached to a ped if( pHitEntity->GetIsPhysical() ) { const CPhysical* pHitPhysical = static_cast(pHitEntity); const CEntity* pAttachedEntity = pHitPhysical->GetIsAttached() ? static_cast(pHitPhysical->GetAttachParent()) : NULL; if( pAttachedEntity && pAttachedEntity->GetIsTypePed() ) return false; fragInst* pFragInst = pHitPhysical->GetFragInst(); if( pFragInst && pFragInst->GetCached() ) { // The component we hit has been broken, so don't try to attach as the matrix will be zeroed out if( pFragInst->GetCacheEntry()->GetHierInst() && pFragInst->GetChildBroken( iStickComponent ) ) { return false; } phBoundComposite* pBoundComposite = static_cast( pFragInst->GetArchetype()->GetBound() ); if( pBoundComposite ) { if( pHitPhysical->IsHiddenFlagSet( iStickComponent ) ) { //Detect how much has broken and when we overlap, detach from my parent entity spdSphere smashSphere( CVehicleGlassManager::GetVehicleGlassComponentSmashSphere( pHitPhysical, iStickComponent, false ) ); if( projectileSphere.IntersectsSphere( smashSphere ) ) { return false; } } } } } } return true; } void CProjectile::StickToEntity( CEntity* pStickEntity, Vec3V_In vStickPosition, Vec3V_In vStickNormal, s32 iStickComponent, phMaterialMgr::Id iStickMaterialId) { Assert( pStickEntity ); Assert( !IsZeroAll( vStickPosition ) ); Assert( !IsZeroAll( vStickNormal ) ); Assert( iStickComponent > -1 ); m_pStickEntity = pStickEntity; m_vStickPos = VEC3V_TO_VECTOR3( vStickPosition ); m_vStickNormal = VEC3V_TO_VECTOR3( vStickNormal ); m_iStickComponent = iStickComponent; m_iStickMaterialId = static_cast(iStickMaterialId); #if __BANK if (pStickEntity) { weaponDebugf3("[projectile] StickToEntity(): m_pStickEntity - %s[%p], m_vStickPos - <%.2f,%.2f,%.2f>, m_vStickNormal - <%.2f,%.2f,%.2f>, m_iStickComponent - %i", pStickEntity->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast(pStickEntity)) : pStickEntity->GetModelName(), pStickEntity, m_vStickPos.GetX(),m_vStickPos.GetY(),m_vStickPos.GetZ(), m_vStickNormal.GetX(), m_vStickNormal.GetY(), m_vStickNormal.GetZ(), m_iStickComponent); } #endif StickToEntity(); } bool CProjectile::NetworkStick(bool bStickEntity, CEntity* pStickEntity, Vector3& vStickPosition, Quaternion& stickQuatern, s32 iStickComponent, u32 /*iStickMaterialId*/) { m_pStickEntity = pStickEntity; #if __BANK if (pStickEntity) { weaponDebugf3("[projectile] NetworkStick(): m_pStickEntity - %s[%p], vStickPosition - <%.2f,%.2f,%.2f>, iStickComponent - %i", pStickEntity->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast(pStickEntity)) : pStickEntity->GetModelName(), pStickEntity, vStickPosition.GetX(),vStickPosition.GetY(),vStickPosition.GetZ(), iStickComponent); } #endif if (pStickEntity && AssertVerify(pStickEntity->GetIsPhysical())) { u32 nFlags = ATTACH_STATE_BASIC|ATTACH_FLAG_INITIAL_WARP|ATTACH_FLAG_COL_ON|ATTACH_FLAG_DELETE_WITH_PARENT; m_iStickComponent = iStickComponent; // wake the target entity if (!pStickEntity->GetIsTypePed() && pStickEntity->GetIsPhysical()) { static_cast(pStickEntity)->ActivatePhysics(); } AttachToPhysicalBasic((CPhysical*)pStickEntity, GetHitBoneIndexFromFrag(), nFlags, &vStickPosition, &stickQuatern); } else { if (bStickEntity) { // the projectile is stuck to a non-networked entity, so just stick to the ground at this location if (!PlaceOnGroundProperly()) { return false; } } Matrix34 projM = MAT34V_TO_MATRIX34(GetTransform().GetMatrix()); Quaternion q; q.FromMatrix34(projM); // Attach to the world AttachToWorldBasic(ATTACH_STATE_WORLD|ATTACH_FLAG_INITIAL_WARP|ATTACH_FLAG_COL_ON, projM.d, &q); } OnAttachment(); return true; } void CProjectile::DetachFromStickEntity() { m_pStickEntity = NULL; m_vStickPos.Zero(); m_vStickNormal.Zero(); m_iStickComponent = 0; m_iStickMaterialId = 0; m_iFlags.ClearFlag( PF_ProcessCollisionProbe ); m_iFlags.ClearFlag( PF_Sticked ); DetachFromParent( DETACH_FLAG_ACTIVATE_PHYSICS|DETACH_FLAG_APPLY_VELOCITY ); } bool CProjectile::StickToEntity() { if( !GetIsAttached() && m_pStickEntity && m_uDestroyTime == 0 ) // don't try reattaching this if it has exploded { bool bIsSticky = false; const float fImpulseMag = GetVelocity().Mag(); if(GetInfo() && GetInfo()->GetIsSticky()) { bIsSticky = true; audSoundInitParams initParams; initParams.Position = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); if(GetHash() == ATSTRINGHASH("AMMO_PROXMINE", 0xAF2208A7)) { audSoundInitParams initParams; initParams.Position = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); audSoundSet proxSet; proxSet.Init(ATSTRINGHASH("SPL_PROXMINE_SOUNDS", 0xAD9FD3AA)); if(proxSet.IsInitialised()) { g_WeaponAudioEntity.TriggerStickyBombAttatch( VECTOR3_TO_VEC3V( m_vStickPos ), m_pStickEntity, m_iStickComponent, m_iStickMaterialId, fImpulseMag, GetOwner(), proxSet.Find(ATSTRINGHASH("attach", 0xD6AD822D))); } } else if(GetWeaponFiredFromHash() == ATSTRINGHASH("VEHICLE_WEAPON_MINE", 0x59EAE9A4)) { if(!m_bHasTriggeredAttachSound) { audSoundSet proxSet; proxSet.Init(ATSTRINGHASH("gr_vehicle_proxmine_sounds", 0xBCA4F642)); if(proxSet.IsInitialised()) { g_WeaponAudioEntity.TriggerStickyBombAttatch( VECTOR3_TO_VEC3V( m_vStickPos ), m_pStickEntity, m_iStickComponent, m_iStickMaterialId, fImpulseMag, GetOwner(), proxSet.Find(ATSTRINGHASH("deploy", 0xC9200261))); } m_bHasTriggeredAttachSound = true; } } else if (GetWeaponFiredFromHash() == ATSTRINGHASH("VEHICLE_WEAPON_MINE_KINETIC", 0x3C09584E) || GetWeaponFiredFromHash() == ATSTRINGHASH("VEHICLE_WEAPON_MINE_EMP", 0x69E10D60) || GetWeaponFiredFromHash() == ATSTRINGHASH("VEHICLE_WEAPON_MINE_SPIKE", 0xD96DA06C) || GetWeaponFiredFromHash() == ATSTRINGHASH("VEHICLE_WEAPON_MINE_SLICK", 0x56FACAC7) || GetWeaponFiredFromHash() == ATSTRINGHASH("VEHICLE_WEAPON_MINE_TAR", 0xF4418BA0)) { if (!m_bHasTriggeredAttachSound) { audSoundSet proxSet; proxSet.Init(ATSTRINGHASH("DLC_AW_Vehicle_Mine_Sounds", 0x291A73D2)); if (proxSet.IsInitialised()) { g_WeaponAudioEntity.TriggerStickyBombAttatch(VECTOR3_TO_VEC3V(m_vStickPos), m_pStickEntity, m_iStickComponent, m_iStickMaterialId, fImpulseMag, GetOwner(), proxSet.Find(ATSTRINGHASH("deploy", 0xC9200261))); } m_bHasTriggeredAttachSound = true; } } else { g_WeaponAudioEntity.TriggerStickyBombAttatch( VECTOR3_TO_VEC3V( m_vStickPos ), m_pStickEntity, m_iStickComponent, m_iStickMaterialId, fImpulseMag, GetOwner(), g_WeaponAudioEntity.FindSound(ATSTRINGHASH("sticky_bomb_hit", 2782906396))); } } if(bIsSticky && NetworkInterface::IsGameInProgress() && m_networkIdentifier.IsValid()) { if(NetworkInterface::GetLocalPhysicalPlayerIndex()==m_networkIdentifier.GetPlayerOwner()) { // this can be unset in certain situations (eg a dying player dropping a sticky bomb) if (m_taskSequenceId != -1) { if(CProjectileManager::GetExistingNetSyncProjectile(m_networkIdentifier) == NULL) { //Send network sync array information for this sticky bombs existence and location s32 indexAddedAt =-1; CProjectileManager::AddNetSyncProjectile(indexAddedAt,this); } if (!NetworkUtils::GetNetworkObjectFromEntity(m_pStickEntity) && m_pStickEntity->GetIsTypeObject()) { CObject* pObject = (CObject*)m_pStickEntity.Get(); if (pObject->GetOwnedBy() == ENTITY_OWNEDBY_RANDOM && CNetObjObject::CanBeNetworked(pObject)) { CBaseModelInfo* pModelInfo = pObject->GetBaseModelInfo(); if ( pModelInfo && !pModelInfo->IsStreamedArchetype() ) { NetworkInterface::RegisterObject(pObject, 0, 0); CNetObjObject* pNetObj = static_cast(pObject->GetNetworkObject()); if (pNetObj) { pNetObj->KeepRegistered(); } } } } } } else { //Don't allow sticky until we have received a sync saying it has landed //so we can change the matrix pos before attaching to anything if (m_pOwner && m_pOwner->GetIsTypePed()) { CPed* pPed = static_cast(m_pOwner.Get()); if (m_taskSequenceId == -1) { #if __ASSERT CNetGamePlayer* pPlayerOwner = NetworkInterface::GetPhysicalPlayerFromIndex(m_networkIdentifier.GetPlayerOwner()); Assertf(0, "A networked sticky bomb has no task sequence assigned (FX id: %d, Player Owner %s (%d))", m_networkIdentifier.GetFXId(), pPlayerOwner ? pPlayerOwner->GetLogName() : "?", m_networkIdentifier.GetPlayerOwner()); #endif } else { CProjectileManager::FireOrPlacePendingProjectile(pPed, m_taskSequenceId, this); } } return true; } } // If we've hit a vehicle chassis we need to try and see if we've hit any components other than the chassis if(m_pStickEntity->GetIsTypeVehicle() && m_iStickComponent == 0) { const int NUM_INTERSECTIONS = 8; phIntersection intersections[NUM_INTERSECTIONS]; static dev_float RADIUS = 0.03f; s32 iNumResults = CPhysics::GetLevel()->TestSphere(m_vStickPos, RADIUS, intersections, NULL, ArchetypeFlags::GTA_VEHICLE_TYPE, TYPE_FLAGS_ALL, phLevelBase::STATE_FLAGS_ALL, NUM_INTERSECTIONS); // Go over the intersections for(s32 i = 0; i < iNumResults; i++) { // If the intersection component isn't 0 and it belongs to the same phInst as our hit entity // set this to the new iComponent // The reason for this is that the chassis component and doors etc. overlap collision and we want to // "stick" to the door rather than the chassis in case it gets opened if(intersections[i].GetComponent() != 0 && intersections[i].GetInstance() == m_pStickEntity->GetCurrentPhysicsInst()) { bool bIsCarVoidMaterial = PGTAMATERIALMGR->UnpackMtlId( intersections[i].GetMaterialId() ) == PGTAMATERIALMGR->g_idCarVoid; if(!bIsCarVoidMaterial) { // Change the component to the new one m_iStickComponent = intersections[i].GetComponent(); break; } } } } if(m_pStickEntity->GetIsPhysical()) { // Attachment vars Vector3 vOffset(m_vStickPos); int iStickComponent = m_iStickComponent; // // We are attaching to a physical object // CPhysical* pHitPhysical = static_cast(m_pStickEntity.Get()); fragInst* pFragInst = pHitPhysical->GetFragInst(); if(pFragInst && pFragInst->GetCached()) { // GTAV - B*1953436 - Make sure we use the correct component from the current lod. if( pHitPhysical->GetIsTypePed() ) { CPed* pPed = static_cast( pHitPhysical ); if( pPed->GetRagdollInst() ) { // Maintaining the original behavior of using the passed in component if it can not be mapped // Would bailing out made more sense though? int ragdollComponent = pPed->GetRagdollInst()->MapRagdollLODComponentHighToCurrent( m_iStickComponent ); if (Verifyf(ragdollComponent != -1, "Invalid ragdoll component %d", ragdollComponent)) { iStickComponent = ragdollComponent; } } } if(pFragInst->GetCacheEntry()->GetHierInst()) { if( pFragInst->GetChildBroken( iStickComponent ) ) { // The component we hit has been broken, so don't try to attach as the matrix will be zeroed out return false; } } // If we have hit a driver/passenger of a bike or quad, then just bounce off if(pHitPhysical->GetIsTypeVehicle()) { phBoundComposite* pBoundComposite = static_cast(pFragInst->GetArchetype()->GetBound()); if (pBoundComposite) { CVehicle* pVehicle = static_cast(pHitPhysical); // If this vehicle part is smashed then don't try to attach if( pVehicle->IsHiddenFlagSet( iStickComponent ) ) { spdSphere projectileSphere; const float fProjectileWidth = GetBoundingBoxMax().GetZ() - GetBoundingBoxMin().GetZ(); projectileSphere.Set( RCC_VEC3V( m_vStickPos ), ScalarV( fProjectileWidth ) ); spdSphere smashSphere( CVehicleGlassManager::GetVehicleGlassComponentSmashSphere( pHitPhysical, iStickComponent, false ) ); if( projectileSphere.IntersectsSphere( smashSphere ) ) { return false; } } if (pVehicle->InheritsFromBike() || pVehicle->InheritsFromQuadBike() || pVehicle->InheritsFromAmphibiousQuadBike()) { // If the GTA_VEH_SEAT_INCLUDE_FLAGS is set on this component then we hit a ped driver/passenger if (pBoundComposite->GetIncludeFlags( iStickComponent ) == (ArchetypeFlags::GTA_VEH_SEAT_INCLUDE_FLAGS)) { return false; } } } } } // wake the target entity pHitPhysical->ActivatePhysics(); // Get the bone matrix Matrix34 boneMat; if(pHitPhysical->GetSkeleton()) { pHitPhysical->GetGlobalMtx(GetHitBoneIndexFromFrag(), boneMat); // B*1853481: Frag objects that had switched to a damaged model were returning zeroed out bone matrices and so sticky bombs couldn't stick to them. // Check if a matrix from the undamaged skeleton is zeroed out and if it is, uses the corresponding bone from the damaged skeleton. // This allows sticky bombs to attach to damaged objects like blown up gas tanks. if(boneMat.a.IsZero()) { if(pHitPhysical->GetFragInst() && pHitPhysical->GetFragInst()->GetCacheEntry() && pHitPhysical->GetFragInst()->GetCacheEntry()->GetHierInst() && pHitPhysical->GetFragInst()->GetCacheEntry()->GetHierInst()->damagedSkeleton) { Mat34V boneMatV; pHitPhysical->GetFragInst()->GetCacheEntry()->GetHierInst()->damagedSkeleton->GetGlobalMtx(GetHitBoneIndexFromFrag(), boneMatV); boneMat.Set(MAT34V_TO_MATRIX34(boneMatV)); } } //B*1789795: don't attach if matrix is not orthonormal if (!boneMat.IsOrthonormal(0.05f)) { return false; } } else { boneMat.Set(MAT34V_TO_MATRIX34(pHitPhysical->GetMatrix())); //B*1789795: don't attach if matrix is not orthonormal if (!boneMat.IsOrthonormal(0.05f)) { return false; } } if(!weaponVerifyf(!boneMat.a.IsZero(), "CProjectile::StickToEntity - Attaching to broken off bone on '%s'",pHitPhysical->GetDebugName())) { return false; } //DR_PROJECTILE_ONLY(debugPlayback::AddSimpleMatrix("boneMat", RCC_MAT34V(boneMat), 0.5f)); // Convert the world space offset into bone space boneMat.UnTransform(vOffset); Assertf(vOffset.Mag2() < 1000000.0f, "Invalid offset %.2f specified for attachment (offset too large). vOffset: X:%.2f Y:%.2f Z:%.2f, m_vStickPos: X:%.2f Y:%.2f Z:%.2f, hit bone: %i, bone pos: X:%.2f Y:%.2f Z:%.2f, hit entity: %s", vOffset.Mag2(), vOffset.GetX(), vOffset.GetY(), vOffset.GetZ(), m_vStickPos.GetX(), m_vStickPos.GetY(), m_vStickPos.GetZ(), GetHitBoneIndexFromFrag(), boneMat.d.GetX(), boneMat.d.GetY(), boneMat.d.GetZ(), pHitPhysical->GetDebugName() ); // Rotation offset (preserve as much of the original rotation as possible) Matrix34 m; if (!GetInfo()->GetDontAlignOnStick()) { m.c = m_vStickNormal; m.a.Cross(m.c, VEC3V_TO_VECTOR3( GetTransform().GetB())); m.a.Normalize(); m.b.Cross(m.c, m.a); m.b.Normalize(); } else { m = MAT34V_TO_MATRIX34(GetTransform().GetMatrix()); } m.d = m_vStickPos; m.Dot3x3Transpose(boneMat); #if DR_PROJECTILE_ENABLED Vector3 reTransformed; boneMat.Transform(vOffset, reTransformed); DR_PROJECTILE_ONLY(debugPlayback::AddSimpleLine("vOffset", VECTOR3_TO_VEC3V( reTransformed ), VECTOR3_TO_VEC3V( boneMat.d ), Color32(0,0,0), Color32(0,0,0))); DR_PROJECTILE_ONLY(debugPlayback::AddSimpleSphere("m_vStickPos", VECTOR3_TO_VEC3V( m_vStickPos ), 0.03f, Color32(0,0,0))); DR_PROJECTILE_ONLY(debugPlayback::AddSimpleLine("m_vStickNormal", VECTOR3_TO_VEC3V( m_vStickPos ), VECTOR3_TO_VEC3V( m_vStickPos+m_vStickNormal ), Color32(128,128,128), Color32(0,0,0))); #endif Quaternion q; m.ToQuaternion(q); //B*1784521: Ensure sticky projectile is destroyed with parent u32 nFlags = ATTACH_STATE_BASIC|ATTACH_FLAG_INITIAL_WARP|ATTACH_FLAG_COL_ON|ATTACH_FLAG_DELETE_WITH_PARENT; // Attach to the hit object AttachToPhysicalBasic(pHitPhysical, GetHitBoneIndexFromFrag( m_pStickEntity, iStickComponent ), nFlags, &vOffset, &q); OnAttachment(); } else { // Rotation offset (preserve as much of the original rotation as possible) Matrix34 m; if(!GetInfo()->GetDontAlignOnStick()) { m.c = m_vStickNormal; m.a.Cross(m.c, VEC3V_TO_VECTOR3(GetTransform().GetB())); m.a.Normalize(); m.b.Cross(m.c, m.a); m.b.Normalize(); } else { m = MAT34V_TO_MATRIX34(GetTransform().GetMatrix()); } m.d = m_vStickPos; Quaternion q; q.FromMatrix34(m); // Attach to the world AttachToWorldBasic(ATTACH_STATE_WORLD|ATTACH_FLAG_INITIAL_WARP|ATTACH_FLAG_COL_ON, m.d, &q); OnAttachment(); } } return true; } s16 CProjectile::GetHitBoneIndexFromFrag() const { return GetHitBoneIndexFromFrag( m_pStickEntity, m_iStickComponent); } s16 CProjectile::GetHitBoneIndexFromFrag(const CEntity* pStickEntity, s32 iStickComponent) { // Get the bone from the passed in component - this is the part of the physical we are attaching too s16 iHitBone = 0; if(pStickEntity) { fragInst* pFragInst = pStickEntity->GetFragInst(); if(pFragInst && pFragInst->GetCached()) { if(weaponVerifyf(iStickComponent >= 0 && iStickComponent < pFragInst->GetTypePhysics()->GetNumChildren(), "Trying to index stick component: %d with num children: %d.", iStickComponent, pFragInst->GetTypePhysics()->GetNumChildren())) { fragTypeChild* pChild = pFragInst->GetTypePhysics()->GetAllChildren()[iStickComponent]; iHitBone = (s16) pFragInst->GetType()->GetBoneIndexFromID((s16)pChild->GetBoneID()); if(pStickEntity->GetIsTypeVehicle()) { // If we're hitting vehicle glass we want to attach to the parent bone, otherwise we will lose the attachment // as soon as the window cracks since the bone matrix gets zeroed out // NOTE: We probably want to do this on non-vehicles too if(CVehicleGlassManager::IsComponentSmashableGlass(static_cast(pStickEntity),iStickComponent)) { s16 parentBone = (s16)pFragInst->GetType()->GetSkeletonData().GetParentIndex(iHitBone); if(parentBone >= 0) { iHitBone = parentBone; } } else if( pFragInst->GetCacheEntry()->GetHierInst() && pFragInst->GetChildBroken( iStickComponent ) ) { iHitBone = 0; //B*2056552 Synced component is broken. It's not glass so we don't know if it has a parent, just fall back to root bone } } else if(pStickEntity->GetIsTypeObject()) { if( pFragInst->GetCacheEntry()->GetHierInst() && pFragInst->GetChildBroken( iStickComponent ) ) { physicsDisplayf("B*3196515: Object with sticky bomb is broken!"); } } } } } return iHitBone; } void CProjectile::GetStartAndEndVectorsForProjectile(Vector3& vStart, Vector3& vEnd) const { // Calculate the direction vector of the line Vector3 vDir(m_vOldSpeed); if(vDir.Mag2() == 0.0f) { // If we have no valid last speed - just pretend we are falling down vDir = -ZAXIS; } // Normalise vDir.NormalizeFast(); // Create a start and end position for the test that are based on the current // and last positions extended by the direction vector const Vector3 vThisPosition = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); vStart = vThisPosition - vDir; vEnd = vThisPosition + vDir; } bool CProjectile::GetIsExplodingInAir(bool bCollided, const CEntity* pHitEntity) const { // Check if we are colliding and not doing a Molotov type explosion if(bCollided && GetInfo()->GetExplosionTag() != EXP_TAG_MOLOTOV) { if(!pHitEntity || !pHitEntity->GetIsTypeVehicle()) { // No collision entity (or it's not a vehicle), but we are colliding with something, so we are not exploding in the air return false; } // If we are colliding with an aircraft, force the code to fall through to the line test const CVehicle* pHitVehicle = static_cast(pHitEntity); if(!pHitVehicle->GetIsAircraft()) { return false; } } // Work out how far the line test will be float fDistBelow = GetInfo()->GetGroundFxProbeDistance(); Vector3 vTestPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); // Move it up slightly static dev_float DIST_ABOVE = 0.05f; vTestPos.z += DIST_ABOVE; WorldProbe::CShapeTestProbeDesc probeDesc; probeDesc.SetStartAndEnd(vTestPos, vTestPos - Vector3(0.0f, 0.0f, fDistBelow)); probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_ALL_MAP_TYPES); if(WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc)) { // We have detected a collision, so the explosion isn't counted as being in the air. return false; } // We are exploding in the air return true; } //create events for peds to react against void CProjectile::CreateImpactEvents() { if (m_pInfo && m_pInfo->GetDontFireAnyEvents()) { return; } const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo(m_uWeaponFiredFromHash); if (m_pOwner) { if(!pWeaponInfo || !pWeaponInfo->GetSuppressGunshotEvent()) { Vector3 vPos = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); Vector3 vOwnerPos = VEC3V_TO_VECTOR3(m_pOwner->GetTransform().GetPosition()); if(!m_iFlags.IsFlagSet(PF_UsingLifeTimer) && !m_iFlags.IsFlagSet(PF_UsingLifeTimeAfterImpact)) { bool bMustBeSeenWhenInVehicle = GetInfo()->GetIsSticky(); CEventGunShotBulletImpact eventImpact(m_pOwner, vOwnerPos, vPos, true, m_uWeaponFiredFromHash, bMustBeSeenWhenInVehicle); eventImpact.SetHitPed(m_pHitPed); GetEventGlobalGroup()->Add(eventImpact); } // Intended for friendly Bullet impact events CEventFriendlyFireNearMiss eventFriendlyNearMiss(m_pOwner, vOwnerPos, vPos, true, m_uWeaponFiredFromHash, CEventFriendlyFireNearMiss::FNM_BULLET_IMPACT); GetEventGlobalGroup()->Add(eventFriendlyNearMiss); if (m_pOwner->GetIsTypePed()) { CPed* pPed = static_cast(m_pOwner.Get()); if (pPed->IsPlayer()) { // create a blip on mini map for the impact event. Note that the owner could move before this impact, so this isn't precisely correct. // If sm_fMinDistSqForProjectileSonarBlip is large this could be an issue. if (IsGreaterThanAll(DistSquared(VECTOR3_TO_VEC3V(vPos), VECTOR3_TO_VEC3V(vOwnerPos)), ScalarV(sm_fMinDistSqForProjectileSonarBlip))) { const bool bScriptRequested = false; const bool bOverridePos = false; CMiniMap::CreateSonarBlip(vPos, CMiniMap::sm_Tunables.Sonar.fSoundRange_ProjectileLanding, HUD_COLOUR_RED, pPed, bScriptRequested, bOverridePos); } } } } else if(m_pOwner->GetIsTypePed() && m_pHitPed && pWeaponInfo && pWeaponInfo->GetIsNonViolent()) { CEventAgitated event(static_cast(m_pOwner.Get()), AT_Griefing); m_pHitPed->GetPedIntelligence()->AddEvent(event); } } } void CProjectile::CreatePotentialBlastEvent(float fTimeUntilExplosion) { if (m_pInfo && m_pInfo->GetDontFireAnyEvents()) { return; } // Get the explosion tag const eExplosionTag expTag = GetInfo()->GetExplosionTag(); if (expTag != EXP_TAG_DONTCARE) { bool bIsSmoke = CExplosionInfoManager::m_ExplosionInfoManagerInstance.GetExplosionTagData(expTag).bIsSmokeGrenade; //Get the radius. float fRadius = CExplosionInfoManager::m_ExplosionInfoManagerInstance.GetExplosionTagData(expTag).endRadius; //Calculate the time of explosion. u32 uTimeOfExplosion = fwTimer::GetTimeInMilliseconds() + (u32)(fTimeUntilExplosion * 1000.0f); //Add the event. CEventPotentialBlast event(CAITarget(this), fRadius, uTimeOfExplosion); event.SetIsSmoke(bIsSmoke); GetEventGlobalGroup()->Add(event); //Create the shocking event. CEventShockingPotentialBlast shockingEvent(*this); if(m_iFlags.IsFlagSet(PF_AnyImpactDetected)) { //If the projectile has hit a surface, allow it to be heard. shockingEvent.SetAudioReactionRangeOverride(4.0f); } //Add the shocking event. CShockingEventsManager::Add(shockingEvent); } } void CProjectile::ProcessWhizzByEvents(bool bForceUpdate) { #if __BANK //Keep debug drawing relevant m_bWhizzByEventCheckDebugDrawPending = false; #endif // __BANK if (m_pInfo && m_pInfo->GetDontFireAnyEvents()) { return; } // Early out if not enough time has elapsed for a check const u32 uCurrentTimeMS = fwTimer::GetTimeInMilliseconds(); if( !bForceUpdate && uCurrentTimeMS < m_uLastWhizzByEventCheckTimeMS + s_uWhizzByEventCheckPeriodMS ) { return; } else { m_uLastWhizzByEventCheckTimeMS = uCurrentTimeMS; } // Get current projectile position const Vec3V& vCurrentPosition = GetTransform().GetPosition(); // If last position has not been initialized yet if( m_vLastWhizzByPosition.IsZero() ) { // initialize last position // NOTE: this results in the same last and current on first time through, and that is OK m_vLastWhizzByPosition = VEC3V_TO_VECTOR3(vCurrentPosition); } // Check that the projectile is moving fast enough if( GetVelocity().Mag2() > rage::square(s_fWhizzByEventMinVelocity) ) { // Perform a spatial array query for peds in radius of the segment from last position to current position const int kPedsToAffectMAX = 8; CSpatialArrayNode* results[kPedsToAffectMAX]; int numFound = CPed::ms_spatialArray->FindNearSegment(vCurrentPosition, VECTOR3_TO_VEC3V(m_vLastWhizzByPosition), s_fWhizzByEventRadius, results, kPedsToAffectMAX); if( numFound > 0 ) { // Define the event to be handed out const bool bIsSilent = false; CEventGunShotWhizzedBy whizzedByEvent(m_pOwner, m_vLastWhizzByPosition, VEC3V_TO_VECTOR3(vCurrentPosition), bIsSilent, m_uWeaponFiredFromHash); // Add the event to peds in range CPed::Pool* pPedPool = CPed::GetPool(); for(int i = 0; i < numFound; i++) { CPed* pPed = CPed::GetPedFromSpatialArrayNode(results[i]); if(pPed && pPedPool->IsValidPtr(pPed)) { pPed->GetPedIntelligence()->AddEvent(whizzedByEvent); } } } #if __BANK // For debug rendering m_vLastWhizzByDebugHeadPosition = VEC3V_TO_VECTOR3(vCurrentPosition); m_vLastWhizzByDebugTailPosition = m_vLastWhizzByPosition; m_bWhizzByEventCheckDebugDrawPending = true; #endif // __BANK } // set the last position using the current position for the next check m_vLastWhizzByPosition = VEC3V_TO_VECTOR3(vCurrentPosition); } bool CProjectile::IsComponentPartOfSpine(const CPed& rPed, s32 iComponent) const { s32 iBoneTag = rPed.GetBoneTagFromRagdollComponent(iComponent); return (iBoneTag == BONETAG_SPINE0 || iBoneTag == BONETAG_SPINE1 || iBoneTag == BONETAG_SPINE2 || iBoneTag == BONETAG_SPINE3); } bool CProjectile::GetIsEntityPartOfTrailer(const CEntity* pTargetEntity, const CEntity* pTrailerEntity) const { if (pTrailerEntity && pTrailerEntity->GetIsTypeVehicle()) { const CVehicle* pVehicle = static_cast(pTrailerEntity); if (pVehicle->InheritsFromTrailer()) { const CTrailer* pTrailer = static_cast(pVehicle); const CVehicle* pAttachedVehicle = pTrailer->GetAttachedToParent(); if (pAttachedVehicle && pAttachedVehicle == pTargetEntity) { return true; } } else if (pVehicle->GetAttachedTrailer()) { const CTrailer* pTrailer = static_cast(pVehicle->GetAttachedTrailer()); if (pTargetEntity == pTrailer) { return true; } } } return false; } void CProjectile::ProcessStickyShapeTest( void ) { ProcessStickyShapeTest( VEC3V_TO_VECTOR3( m_matPrevious.GetCol3() ), VEC3V_TO_VECTOR3( GetMatrix().GetCol3() ) ); } void CProjectile::ProcessStickyShapeTest( const Vector3 &vStart, const Vector3 &vEnd ) { const float fProjectileWidth = GetBoundingBoxMax().GetZ() - GetBoundingBoxMin().GetZ(); WorldProbe::CShapeTestCapsuleDesc capsuleDesc; WorldProbe::CShapeTestFixedResults<> capsuleResults; capsuleDesc.SetResultsStructure( &capsuleResults ); Mat34V mat = GetMatrix(); Vector3 vMovement = vEnd - vStart; if (vMovement.IsZero()) { vMovement = VEC3V_TO_VECTOR3( Negate( mat.GetCol2() ) ); } vMovement.Normalize(); capsuleDesc.SetCapsule( vStart, vEnd, fProjectileWidth ); DR_PROJECTILE_ONLY(debugPlayback::AddSimpleLine("CapsuleTest", VECTOR3_TO_VEC3V(vStart), VECTOR3_TO_VEC3V(vEnd), Color32(0,0,0), Color32(255,255,255))); const CEntity* pNoCollisionEntity = (const CEntity*)GetNoCollisionEntity(); static const s32 iNumExceptions = 2; const CEntity* ppExceptions[iNumExceptions]; ppExceptions[0] = this; ppExceptions[1] = pNoCollisionEntity; #if __BANK if (pNoCollisionEntity) weaponDebugf3("[projectile] ProcessStickyShapeTest(): pNoCollisionEntity - %s[%p]",pNoCollisionEntity->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast(pNoCollisionEntity)) : pNoCollisionEntity->GetModelName(), pNoCollisionEntity); #endif capsuleDesc.SetExcludeEntities(ppExceptions,iNumExceptions); capsuleDesc.SetIncludeFlags( (ArchetypeFlags::GTA_WEAPON_TYPES & ~ArchetypeFlags::GTA_PROJECTILE_TYPE & ~ArchetypeFlags::GTA_PED_TYPE & ~ArchetypeFlags::GTA_RIVER_TYPE) ); capsuleDesc.SetTypeFlags( ArchetypeFlags::GTA_WEAPON_TEST ); capsuleDesc.SetIsDirected( true ); capsuleDesc.SetTreatPolyhedralBoundsAsPrimitives(false); bool bCheckResults = false; bool bIgnoreWheelSphereExclusion = false; if( WorldProbe::GetShapeTestManager()->SubmitTest( capsuleDesc ) ) { bCheckResults = true; } // Check in the opposite direction to compensate for interior bounds (vehicles mostly) that do not have collision else { capsuleDesc.SetCapsule( vEnd, vStart, fProjectileWidth ); if( WorldProbe::GetShapeTestManager()->SubmitTest( capsuleDesc ) ) { bCheckResults = true; bIgnoreWheelSphereExclusion = true; } } if( bCheckResults ) { //Find the first contact along movement int nNumHitResults = capsuleResults.GetNumHits(); float fMinDot = FLT_MAX; int iBestHit = -1; for( int i = 0; i < nNumHitResults; ++i ) { CEntity* pStickEntity = capsuleResults[i].GetHitEntity(); if( m_ignoreDamageEntityAttachParent ) { #if __BANK if (m_pIgnoreDamageEntity) { weaponDebugf3("[projectile] ProcessStickyShapeTest(): m_ignoreDamageEntityAttachParent - true, m_pIgnoreDamageEntity - %s[%p], pStickEntity - %s[%p]", m_pIgnoreDamageEntity.Get()->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast(m_pIgnoreDamageEntity.Get())) : m_pIgnoreDamageEntity.Get()->GetModelName(),m_pIgnoreDamageEntity.Get(), pStickEntity && pStickEntity->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast(pStickEntity)) : (pStickEntity ? pStickEntity->GetModelName() : ""),pStickEntity); } #endif if (m_pIgnoreDamageEntity && pStickEntity && pStickEntity->GetAttachParent() == m_pIgnoreDamageEntity ) { #if __BANK weaponDebugf3("[projectile] ProcessStickyShapeTest: Ignoring impact with entity %s[%p], because its attach parent is %s[%p]", pStickEntity->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast(pStickEntity)) : pStickEntity->GetModelName(), pStickEntity, m_pIgnoreDamageEntity.Get()->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast(m_pIgnoreDamageEntity.Get())) : m_pIgnoreDamageEntity.Get()->GetModelName(), m_pIgnoreDamageEntity.Get()); #endif continue; } } if( pStickEntity && CProjectile::ShouldStickToEntity( pStickEntity, GetOwner(), fProjectileWidth, capsuleResults[i].GetHitPositionV(), capsuleResults[i].GetHitComponent(), capsuleResults[i].GetHitMaterialId(), bIgnoreWheelSphereExclusion, GetInfo()->GetShouldStickToPeds(), m_bHitPedFriendly ) ) { float fTestDot = capsuleResults[i].GetHitPosition().Dot(vMovement); if (fTestDot < fMinDot) { iBestHit = i; fMinDot = fTestDot; } } } if (iBestHit > -1) { m_pStickEntity = capsuleResults[iBestHit].GetHitEntity(); m_iStickComponent = capsuleResults[iBestHit].GetHitComponent(); m_iStickMaterialId = capsuleResults[iBestHit].GetHitMaterialId(); //Okay capsuleResults[iBestHit].GetHitNormal(); is our fallback normal but lets try probing to average out bumps a little lower down m_vStickNormal = capsuleResults[iBestHit].GetHitNormal(); // Push back the position slightly to match up with the projectile mesh (since all objects use the root position) Vec3V vHitPos = capsuleResults[iBestHit].GetHitPositionV(); Vector3 vPositionNudge = m_vStickNormal; vPositionNudge.Scale( fProjectileWidth * 0.5f ); // Check if we should adjust the position nudge; super hack due to female MP player ragdoll bounds not matching up well if (NetworkInterface::IsGameInProgress() && m_pStickEntity && m_pStickEntity->GetIsTypePed()) { CPed* pStickPed = static_cast(m_pStickEntity.Get()); if (!pStickPed->IsMale() && pStickPed->IsPlayer() && IsComponentPartOfSpine(*pStickPed, m_iStickComponent) && !CTaskParachute::IsParachutePackVariationActiveForPed(*pStickPed)) { static dev_float s_fAdjustment = 0.05f; Vector3 vAdjustment = m_vStickNormal; vAdjustment.Negate(); vAdjustment.Scale(s_fAdjustment); vPositionNudge += vAdjustment; } } if (m_pStickEntity) { Vec3V vNormal = RCC_VEC3V(m_vStickNormal); FindAveragedStickySurfaceNormal(m_pOwner, *m_pStickEntity, vHitPos, vNormal, fProjectileWidth); m_vStickNormal = VEC3V_TO_VECTOR3(vNormal); // GTAV B*1953436 - if the entity is a ped make sure we store the high lod // component. We can then map it to the correct current lod when we attach it if( m_pStickEntity->GetIsTypePed() ) { CPhysical* pHitPhysical = static_cast(m_pStickEntity.Get()); fragInst* pFragInst = pHitPhysical->GetFragInst(); if(pFragInst && pFragInst->GetCached()) { int ragdollComponent = fragInstNM::MapRagdollLODComponentCurrentToHigh(m_iStickComponent, pFragInst->GetCurrentPhysicsLOD()); if (weaponVerifyf(ragdollComponent != -1, "Invalid ragdoll component %d", ragdollComponent)) { m_iStickComponent = ragdollComponent; } } } } DR_PROJECTILE_ONLY(debugPlayback::AddSimpleSphere("_position nudge", vHitPos, 0.01f, Color32(255,255,255))); m_vStickPos = VEC3V_TO_VECTOR3(vHitPos + VECTOR3_TO_VEC3V(vPositionNudge)); DR_PROJECTILE_ONLY(debugPlayback::AddSimpleLine("position nudge", vHitPos, VECTOR3_TO_VEC3V(m_vStickPos), Color32(255,255,255), Color32(255,255,255))); #if __BANK CEntity* pHitEntity = capsuleResults[iBestHit].GetHitEntity(); if (pHitEntity) { weaponDebugf3("[projectile] ProcessStickyShapeTest(): m_pStickEntity - %s[%p], m_vStickPos - <%.2f,%.2f,%.2f>, m_vStickNormal - <%.2f,%.2f,%.2f>, m_iStickComponent - %i", pHitEntity->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast(pHitEntity)) : pHitEntity->GetModelName(), pHitEntity, m_vStickPos.GetX(),m_vStickPos.GetY(),m_vStickPos.GetZ(), m_vStickNormal.GetX(),m_vStickNormal.GetY(),m_vStickNormal.GetZ(), m_iStickComponent); } #endif } } } bool CProjectile::FindAveragedStickySurfaceNormal(const CEntity *pThrower, const CEntity &rStickEntity, Vec3V_In vHitPos, Vec3V_InOut rNormalInAndOut, float fProjectileWidth) { DR_PROJECTILE_ONLY(debugPlayback::AddSimpleSphere("inhitpos", vHitPos, 0.02f, Color32(255,255,255))); DR_PROJECTILE_ONLY(debugPlayback::AddSimpleLine("inhit", vHitPos, vHitPos+rNormalInAndOut, Color32(128,128,128), Color32(0,0,0))); //a little set of points to see what we get Vec3V vAdjustProbeN = rNormalInAndOut; if (pThrower) { //Just use the thrower's forward direction to try and stick to something vAdjustProbeN = pThrower->GetTransform().GetB(); } else { //Bias towards centre of object (to avoid some of the influence of dodgy //external normals) Vec3V vAdjustProbe = rStickEntity.GetTransform().GetPosition() - vHitPos; vAdjustProbeN = rNormalInAndOut - Normalize(vAdjustProbe); vAdjustProbeN = Negate( Normalize(vAdjustProbeN) ); } WorldProbe::CShapeTestCapsuleDesc capsuleDesc; capsuleDesc.SetIncludeFlags( (ArchetypeFlags::GTA_ALL_TYPES_WEAPON & ~ArchetypeFlags::GTA_PROJECTILE_TYPE & ~ArchetypeFlags::GTA_PED_TYPE & ~ArchetypeFlags::GTA_RIVER_TYPE) ); capsuleDesc.SetTypeFlags( ArchetypeFlags::GTA_WEAPON_TEST ); capsuleDesc.SetIsDirected( true ); capsuleDesc.SetTreatPolyhedralBoundsAsPrimitives(false); capsuleDesc.SetDomainForTest(WorldProbe::TEST_AGAINST_INDIVIDUAL_OBJECTS); capsuleDesc.SetIncludeInstance( rStickEntity.GetCurrentPhysicsInst() ); //Probe a little patch Vec3V vOffsets[4]; //vOffsets[0] = Vec3V(0.0f, fProjectileWidth*0.5f, 0.0f); //vOffsets[1] = Vec3V(0.05f, -fProjectileWidth*0.5f, 0.0f); //vOffsets[2] = Vec3V(-fProjectileWidth*0.5f,-fProjectileWidth*0.5f, 0.0f); //TMS: Pulled in slightly from the edges, allow some penetration there if the overall normal looks good vOffsets[0] = Vec3V(-fProjectileWidth*0.8f, 0.0f, 0.0f); vOffsets[1] = Vec3V(+fProjectileWidth*0.8f, 0.0f, 0.0f); vOffsets[2] = Vec3V(0.0f, fProjectileWidth*0.4f, 0.0f); vOffsets[3] = Vec3V(0.0f,-fProjectileWidth*0.4f, 0.0f); Vec3V vResults[4]; int iNumResults=0; //Build a matrix to offset these in a way that might find us a hit result Mat34V matTrans; matTrans.SetCol3( vHitPos ); if (Abs(vAdjustProbeN.GetZf()) > 0.5f) { matTrans.SetCol0( Cross(Vec3V(V_X_AXIS_WZERO), vAdjustProbeN) ); } else { matTrans.SetCol0( Cross(Vec3V(V_Z_AXIS_WZERO), vAdjustProbeN) ); } matTrans.SetCol1( Cross(matTrans.GetCol0(), vAdjustProbeN)) ; matTrans.SetCol2( Cross(matTrans.GetCol1(), matTrans.GetCol0() ) ); ReOrthonormalize3x3(matTrans, matTrans); for (int i=0 ; i capsuleResults2; capsuleDesc.SetResultsStructure( &capsuleResults2 ); Vec3V vPoint = Transform(matTrans, vOffsets[i]); vPoint = vPoint - vAdjustProbeN * ScalarV(0.5f); DR_PROJECTILE_ONLY(debugPlayback::AddSimpleLine("normalprobetest", vPoint, vPoint+vAdjustProbeN, Color32(255,0,0), Color32(0,255,0))); capsuleDesc.SetCapsule( RCC_VECTOR3(vPoint), VEC3V_TO_VECTOR3(vPoint+vAdjustProbeN), 0.01f ); if( WorldProbe::GetShapeTestManager()->SubmitTest( capsuleDesc ) ) { ScalarV vMinDot(V_FLT_MAX); Vec3V vBestPos; int iBestHit = -1; int nNumHitResults2 = capsuleResults2.GetNumHits(); for( int ii = 0; ii < nNumHitResults2; ++ii ) { if(&rStickEntity == capsuleResults2[ii].GetHitEntity()) { Vec3V vTestPos = capsuleResults2[ii].GetHitPositionV(); ScalarV vTestDot = Dot( vAdjustProbeN, vTestPos ); if (IsLessThanAll(vTestDot, vMinDot)) { iBestHit = ii; vMinDot = vTestDot; vBestPos = vTestPos; } } } if (iBestHit > -1) { DR_PROJECTILE_ONLY(debugPlayback::AddSimpleSphere("normalprobepos", vBestPos, 0.01f, Color32(0,0,255))); vResults[iNumResults] = vBestPos; ++iNumResults; } } } if (iNumResults == COUNTOF(vResults)) { //New normal from averaged positions! //rNormalInAndOut = Cross(vResults[2] - vResults[1], vResults[1] - vResults[0]); rNormalInAndOut = Cross(vResults[3] - vResults[2], vResults[1] - vResults[0]); rNormalInAndOut = Normalize(rNormalInAndOut); DR_PROJECTILE_ONLY(debugPlayback::AddSimpleLine("newnormal", vHitPos, vHitPos + rNormalInAndOut, Color32(255,0,0), Color32(0,0,0))); return true; } else { DR_PROJECTILE_ONLY(debugPlayback::AddSimpleLine("useoriginalnormal", vHitPos, vHitPos + rNormalInAndOut, Color32(255,0,0), Color32(0,0,0))); } return false; } // Destroys the projectile and causes it to explode. bool CProjectile::ObjectDamage(float fImpulse, const Vector3* pColPos, const Vector3* pColNormal, CEntity* pEntityResponsible, u32 nWeaponUsedHash, phMaterialMgr::Id hitMaterial) { bool bAttachedToPed = GetAttachParent() && GetAttachParent()->GetType() == ENTITY_TYPE_PED; bool bIsSticky = m_uWeaponFiredFromHash == WEAPONTYPE_STICKYBOMB.GetHash(); // don't let projectiles be destroyed when attached to a ped or are ordnances (...unless it's a sticky bomb on a ped!) if (GetIsOrdnance() || (bAttachedToPed && !bIsSticky)) { return false; } if( NetworkInterface::IsGameInProgress() && bIsSticky ) { if(m_iFlags.IsFlagSet(PF_StuckToSpectatorPedOrGhostVeh)) { return false; } if( bAttachedToPed && pEntityResponsible && pEntityResponsible->GetIsTypePed() && static_cast(pEntityResponsible)->IsAPlayerPed() && static_cast(GetAttachParent())->IsAPlayerPed() ) { const CPed *pVictimPlayer = static_cast(GetAttachParent()); if(!NetworkInterface::IsFriendlyFireAllowed(pVictimPlayer, pEntityResponsible)) { return false; } } } // don't blow up if flagged not to if (m_pInfo && !m_pInfo->GetCanBeDestroyedByDamage()) { return false; } // don't let impacts from vehicles cause explosions if (nWeaponUsedHash == WEAPONTYPE_RAMMEDBYVEHICLE.GetHash() || nWeaponUsedHash == WEAPONTYPE_RUNOVERBYVEHICLE.GetHash()) { return false; } // only destroy the object if the base CObject damage call is successful if (CObject::ObjectDamage(fImpulse, pColPos, pColNormal, pEntityResponsible, nWeaponUsedHash, hitMaterial)) { //Decrement the owners sticky count before passing ownership if(m_pOwner && m_pOwner->GetIsTypePed()) { CPed* pPedOwner = static_cast(m_pOwner.Get()); if(m_pInfo && m_pInfo->GetIsSticky()) { pPedOwner->DecrementStickyCount(); //Set flag so new owner doesn't decrement their sticky bomb count m_iFlags.SetFlag(PF_NoStickyBombOwnership); } } // Change the culprit to the entity who triggered the explosion. if (pEntityResponsible) m_pOwner = pEntityResponsible; m_iFlags.SetFlag(PF_ExplodeNextFrame); if (m_pInfo->GetIsSticky() && !m_iFlags.IsFlagSet(PF_Active)) { m_iFlags.SetFlag(PF_Active); } return true; } return false; } // Init the lifetime values (separated as it didn't always hit in Fire) void CProjectile::InitLifetimeValues(const f32 fLifeTime) { const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo(m_uWeaponFiredFromHash); if (fLifeTime >= 0.0f) { m_fLifeTime = fLifeTime; } else { m_fLifeTime = GetInfo()->GetLifeTime(); //B*2133442: Increase lifetime of rockets in MP if locked on and GetLifeTimePlayerLockedOverrideMP parameter is set on the rocket ammo info. if (NetworkInterface::IsGameInProgress() && m_pOwner && m_pOwner->GetIsTypeVehicle() && pWeaponInfo && pWeaponInfo->GetAmmoInfo() && pWeaponInfo->GetAmmoInfo()->GetClassId() == CAmmoRocketInfo::GetStaticClassId()) { const CAmmoRocketInfo *pRocketInfo = static_cast(pWeaponInfo->GetAmmoInfo()); if (pRocketInfo && pRocketInfo->GetLifeTimePlayerVehicleLockedOverrideMP() != -1.0f) { if (GetAsProjectileRocket() && GetAsProjectileRocket()->GetTarget()) { m_fLifeTime = pRocketInfo->GetLifeTimePlayerVehicleLockedOverrideMP(); } } } } // If we've fired an on-foot homing projectile and aren't locked on, clamp the projectile lifetime to 10 seconds. bool bUsingOnFootHomingWeapon = pWeaponInfo && pWeaponInfo->GetIsOnFootHoming(); if (bUsingOnFootHomingWeapon && GetAsProjectileRocket()) { static dev_float fMaxUnlockedLifeTime = 10.0f; if (GetAsProjectileRocket() && !GetAsProjectileRocket()->GetWasLockedOnWhenFired() && m_fLifeTime > fMaxUnlockedLifeTime) { m_fLifeTime = fMaxUnlockedLifeTime; } } m_fLifeTimeAfterImpact = GetInfo()->GetLifeTimeAfterImpact(); if(m_fLifeTime >= 0.0f && m_fLifeTimeAfterImpact == 0.0f && (!GetInfo() || !GetInfo()->GetDelayUntilSettled())) { // Set that we are using the life timer m_iFlags.SetFlag(PF_UsingLifeTimer); } m_fExplosionTime = GetInfo()->GetExplosionTime(); if(m_fExplosionTime == 0.0f) { m_fExplosionTime = m_fLifeTime; } if(m_fLifeTime <= m_fLifeTimeAfterImpact && pWeaponInfo && pWeaponInfo->GetDropWhenCooked()) { m_fLifeTimeAfterImpact = m_fLifeTime; } } bool CProjectile::ProcessProximityMine() { ProcessProximityMineActivation(); #if DEBUG_DRAW TUNE_GROUP_BOOL(PROXIMITY_MINES, bRenderProximityMineDebug, false); if (bRenderProximityMineDebug) { float fProximityRadius = m_pInfo->GetProximityTriggerRadius(); if (fProximityRadius <= 0.0f) { // If not defined in projectile info, use the explosion radius CExplosionTagData& expTagData = CExplosionInfoManager::m_ExplosionInfoManagerInstance.GetExplosionTagData(GetInfo()->GetExplosionTag()); fProximityRadius = CExplosionInfoManager::m_ExplosionInfoManagerInstance.GetExplosionEndRadius(expTagData); } grcDebugDraw::Sphere(GetTransform().GetPosition(), fProximityRadius, m_bProximityMineActive ? (m_bProximityMineTriggered ? Color_red : Color_green) : Color_orange, false); } #endif // DEBUG_DRAW if (m_bProximityMineActive) { ProcessProximityMineTrigger(); if (m_bProximityMineTriggered) { // Wait for timer before exploding. float fExplodeTime = m_pInfo->GetProximityFuseTimePed(); if (m_bProximityMineTriggeredByVehicle) { float fExplodeTimeVehicleSpeed = m_pInfo->GetProximityFuseTimeVehicleSpeed(); float fScalar = Clamp(m_bProximityMineTriggeredByVehicleSpeed, 0.0f, fExplodeTimeVehicleSpeed) / fExplodeTimeVehicleSpeed; fExplodeTime = Lerp(fScalar, m_pInfo->GetProximityFuseTimeVehicleMin(), m_pInfo->GetProximityFuseTimeVehicleMax()); } if (m_fProximityExplodeTimer >= fExplodeTime) { Displayf("CProjectile::ProcessControl: TriggerExplosion(). Reason: m_fProximityExplodeTimer >= fExplodeTime."); m_bProximityMineTriggered = false; m_bProximityMineTriggeredByVehicle = false; m_bProximityMineActive = false; m_fProximityExplodeTimer = 0.0f; m_fProximityMineStuckTime = 0.0f; m_iFlags.SetFlag(PF_ForceExplosion); TriggerExplosion(); return true; } m_fProximityExplodeTimer += fwTimer::GetTimeStep(); } } return false; } void CProjectile::ProcessProximityMineActivation() { #if DEBUG_DRAW TUNE_GROUP_BOOL(PROXIMITY_MINES, bRenderProximityMineActivationDebug, false); #endif // DEBUG_DRAW // No need to run any of this if we're already active if (m_bProximityMineActive) { return; } // Allow script to turn off the whole safe mode system via server tunable if (!sm_bProximityMineUseActivationSafeMode) { m_bProximityMineActivationSafeMode = false; } // Don't run safe mode checks if we already disabled it by leaving at any point if (m_bProximityMineActivationSafeMode) { // While safe mode is active, block activation of proximity mine if we're still within the radius bool bOwnerCurrentlyWithinRadius = false; if (m_pOwner && m_pOwner->GetIsTypePed()) { const CPed* pPedOwner = static_cast(m_pOwner.Get()); Vec3V vBombPosition = GetTransform().GetPosition(); float fProximityRadius = m_pInfo->GetProximityTriggerRadius(); if (fProximityRadius <= 0.0f) { // If not defined in projectile info, use the explosion radius CExplosionTagData& expTagData = CExplosionInfoManager::m_ExplosionInfoManagerInstance.GetExplosionTagData(GetInfo()->GetExplosionTag()); fProximityRadius = CExplosionInfoManager::m_ExplosionInfoManagerInstance.GetExplosionEndRadius(expTagData); } if (pPedOwner->GetIsInVehicle()) { const CVehicle* pOwnerVehicle = pPedOwner->GetMyVehicle(); // If the owner is in a vehicle, we need to check against vehicle bounds for better accuracy spdAABB tempBox; spdAABB vehLocalBounds = pOwnerVehicle->GetLocalSpaceBoundBox(tempBox); Vec3V vBombPositionLocalToVeh = UnTransformOrtho(pOwnerVehicle->GetMatrix(), vBombPosition); spdSphere proxSphere(vBombPositionLocalToVeh, ScalarV(fProximityRadius)); if (vehLocalBounds.IntersectsSphere(proxSphere)) { bOwnerCurrentlyWithinRadius = true; #if DEBUG_DRAW if (bRenderProximityMineActivationDebug) { grcDebugDraw::BoxOriented(vehLocalBounds.GetMin(), vehLocalBounds.GetMax(), pOwnerVehicle->GetMatrix(), Color_orange, false); } #endif // DEBUG_DRAW } } else { // Otherwise, just do a basic distance check Vec3V vOwnerPosition = pPedOwner->GetTransform().GetPosition(); float fDistanceSq = MagSquared(vBombPosition - vOwnerPosition).Getf(); if (fDistanceSq < square(fProximityRadius)) { bOwnerCurrentlyWithinRadius = true; #if DEBUG_DRAW if (bRenderProximityMineActivationDebug) { grcDebugDraw::Sphere(vOwnerPosition, 0.25f, Color_orange, false); } #endif // DEBUG_DRAW } } } // If the owner is outside the radius at any point, we should disable safe mode and mine should activate (once the timer is up) // This is so we don't block activation when throwing a proximity mine at a far distance, or if the owner leaves and re-enters while activation timer is still running if (!bOwnerCurrentlyWithinRadius) { m_bProximityMineActivationSafeMode = false; } } float fActivationTime = m_bProximityMineRepeatingDetonation ? m_pInfo->GetProximityRepeatedDetonationActivationTime() : m_pInfo->GetProximityActivationTime(); if (!m_bProximityMineActivationSafeMode && m_fProximityMineStuckTime >= fActivationTime) { m_bProximityMineActive = true; } // Always update timer whether safe mode is active or not if (m_fProximityMineStuckTime < fActivationTime) { m_fProximityMineStuckTime += fwTimer::GetTimeStep(); } } void CProjectile::ProcessProximityMineTrigger() { #if DEBUG_DRAW TUNE_GROUP_BOOL(PROXIMITY_MINES, bRenderProximityMineTriggerDebug, false); #endif // DEBUG_DRAW // No need to run any of this if we're already triggered if (m_bProximityMineTriggered) { return; } Vec3V vBombPosition = GetTransform().GetPosition(); float fProximityRadius = m_pInfo->GetProximityTriggerRadius(); if (fProximityRadius <= 0.0f) { // If not defined in projectile info, use the explosion radius CExplosionTagData& expTagData = CExplosionInfoManager::m_ExplosionInfoManagerInstance.GetExplosionTagData(GetInfo()->GetExplosionTag()); fProximityRadius = CExplosionInfoManager::m_ExplosionInfoManagerInstance.GetExplosionEndRadius(expTagData); } bool bTriggered = false; // Use the entity iterator to gather a list of nearby vehicles and peds // We need to search slightly wider than the radius so that we can catch the root position of large vehicles that may be crossing trigger bounds TUNE_GROUP_FLOAT(PROXIMITY_MINES, fAdditionalTriggerScanRadius, 5.0f, 0.0f, 20.0f, 0.01f); s32 iteratorFlags = IterateVehicles; if (m_pInfo->GetProximityCanBeTriggeredByPeds()) { iteratorFlags |= IteratePeds; } CEntityIterator entityIterator(IteratePeds|IterateVehicles, NULL, &vBombPosition, fProximityRadius + fAdditionalTriggerScanRadius); for (CEntity* pEntity = entityIterator.GetNext(); pEntity; pEntity = entityIterator.GetNext()) { if (pEntity->GetIsTypeVehicle()) { const CVehicle* pTriggerVehicle = static_cast(pEntity); // Ignore vehicles if script turns off new vehicle detection code via server tunable if (!sm_bProximityMineUseBetterVehicleDetection) { continue; } // Ignore if the vehicle is not moving if (pTriggerVehicle->GetVelocity().XYMag2() < SMALL_FLOAT) { continue; } if (pTriggerVehicle->GetDriver() && pTriggerVehicle->GetDriver()->IsPlayer()) { // Ignore if remote player driver (as detection should be done on each local machine for accuracy) if (!pTriggerVehicle->GetDriver()->IsLocalPlayer()) { continue; } // Ignore if player driver is ghosted / in passive mode if (NetworkInterface::IsGameInProgress() && pTriggerVehicle->GetDriver()->GetNetworkObject()) { CNetObjPlayer* pNearbyNetObjPlayer = static_cast(pTriggerVehicle->GetDriver()->GetNetworkObject()); if (pNearbyNetObjPlayer && pNearbyNetObjPlayer->IsPassiveMode()) { continue; } } } if (!m_pInfo->GetProximityAffectsFiringPlayer() && m_pOwner && m_pOwner->GetIsTypePed()) { const CPed* pPedOwner = static_cast(m_pOwner.Get()); const CVehicle* vOwnerVehicle = pPedOwner->GetVehiclePedInside(); if (vOwnerVehicle == pTriggerVehicle) { continue; } } // Check against vehicle bounds to see if we're intersecting our trigger sphere // If the owner is in a vehicle, we need to check against vehicle bounds for better accuracy spdAABB tempBox; spdAABB vehLocalBounds = pTriggerVehicle->GetLocalSpaceBoundBox(tempBox); Vec3V vBombPositionLocalToVeh = UnTransformOrtho(pTriggerVehicle->GetMatrix(), vBombPosition); spdSphere proxSphere(vBombPositionLocalToVeh, ScalarV(fProximityRadius)); // Scale down bounding box slightly to get closer to previous behaviour (when the driver inside the vehicle was the trigger entity) TUNE_GROUP_FLOAT(PROXIMITY_MINES, fVehicleTriggerBoundsScale, 0.5f, 0.01f, 1.0f, 0.01f); vehLocalBounds.SetMin(vehLocalBounds.GetMin() * ScalarV(fVehicleTriggerBoundsScale)); vehLocalBounds.SetMax(vehLocalBounds.GetMax() * ScalarV(fVehicleTriggerBoundsScale)); #if DEBUG_DRAW if (bRenderProximityMineTriggerDebug) { grcDebugDraw::BoxOriented(vehLocalBounds.GetMin(), vehLocalBounds.GetMax(), pTriggerVehicle->GetMatrix(), Color_red, false); } #endif // DEBUG_DRAW if (vehLocalBounds.IntersectsSphere(proxSphere)) { // SUCCESS! bTriggered = true; // Take note of our speed, as we'll use this to adjust trigger fuse time later m_bProximityMineTriggeredByVehicle = true; m_bProximityMineTriggeredByVehicleSpeed = pTriggerVehicle->GetVelocity().XYMag(); // Don't need to check any more entities break; } } else if (pEntity->GetIsTypePed()) { const CPed* pTriggerPed = static_cast(pEntity); // Ignore if in vehicle (as we're doing separate vehicle entity checks to detect those, unless the system is turned off) if (pTriggerPed->GetIsInVehicle() && sm_bProximityMineUseBetterVehicleDetection) { continue; } // Ignore if ped is dead and hasn't died very recently if (pTriggerPed->ShouldBeDead() && fwTimer::GetTimeInMilliseconds() > pTriggerPed->GetTimeOfDeath() + 2000) { continue; } if (pTriggerPed->IsPlayer()) { // Ignore if remote player (as detection should be done on each local machine for accuracy) if (!pTriggerPed->IsLocalPlayer()) { continue; } if (!m_pInfo->GetProximityAffectsFiringPlayer() && m_pOwner && m_pOwner->GetIsTypePed()) { const CPed* pPedOwner = static_cast(m_pOwner.Get()); if (pPedOwner->IsLocalPlayer()) { continue; } } // Ignore if player is ghosted / in passive mode if (NetworkInterface::IsGameInProgress() && pTriggerPed->GetNetworkObject()) { CNetObjPlayer* pNearbyNetObjPlayer = static_cast(pTriggerPed->GetNetworkObject()); if (pNearbyNetObjPlayer && pNearbyNetObjPlayer->IsPassiveMode()) { continue; } } } // Ignore if too far away from actual trigger radius float fPedDistanceSq = MagSquared(vBombPosition - pTriggerPed->GetTransform().GetPosition()).Getf(); if (fPedDistanceSq > square(fProximityRadius)) { continue; } // Make sure we have line of sight between the ped and the proximity mine WorldProbe::CShapeTestHitPoint probeIsect; WorldProbe::CShapeTestResults probeResult(probeIsect); WorldProbe::CShapeTestProbeDesc probeDesc; probeDesc.SetStartAndEnd(VEC3V_TO_VECTOR3(vBombPosition), VEC3V_TO_VECTOR3(pTriggerPed->GetTransform().GetPosition())); probeDesc.SetResultsStructure(&probeResult); probeDesc.SetExcludeEntity(pTriggerPed); probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_MAP_TYPE_MOVER); probeDesc.SetContext(WorldProbe::LOS_Weapon); #if DEBUG_DRAW if (bRenderProximityMineTriggerDebug) { grcDebugDraw::Line(vBombPosition, pTriggerPed->GetTransform().GetPosition(), Color_red); } #endif // DEBUG_DRAW if (!WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc, WorldProbe::PERFORM_SYNCHRONOUS_TEST)) { // SUCCESS! bTriggered = true; // Don't need to check any more entities break; } } } if (bTriggered) { m_bProximityMineTriggered = true; audSoundSet proxSet; const u32 soundSetNameHash = GetMineSoundsetHash(GetWeaponFiredFromHash()); if(proxSet.Init(soundSetNameHash)) { // Play trigger audio audSoundInitParams initParams; initParams.Position = VEC3V_TO_VECTOR3(GetTransform().GetPosition()); g_WeaponAudioEntity.CreateAndPlaySound(proxSet.Find(ATSTRINGHASH("trigger", 0xC3AFD061)), &initParams); if (NetworkInterface::IsGameInProgress()) { CGameScriptId scriptId; CPlaySoundEvent::Trigger(initParams.Position, soundSetNameHash, ATSTRINGHASH("trigger", 0xC3AFD061), 0xff, scriptId, 50); } } } }