Files
GTASource/game/weapons/projectiles/Projectile.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

4715 lines
167 KiB
C++

// 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<const CObject*>(&rEnt);
if (!pObject->GetIsVisible() && pObject->GetAttachParent() &&
static_cast<CEntity*>(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<const CAmmoProjectileInfo*>(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<CWeaponModelInfo*>(pBaseModelInfo)->GetDampingTunedViaTunableObjects();
}
if (bSetDamping)
{
phInst* pInst = GetCurrentPhysicsInst();
if (pInst)
{
phArchetype* pArch = pInst->GetArchetype();
if (pArch)
{
phArchetypeDamp* pInstArchDamp = dynamic_cast<phArchetypeDamp*>(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<CPed*>( 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<CPed*>(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<CPed*>(m_pStickEntity.Get());
if (pStuckPed && pStuckPed->GetNetworkObject() && (static_cast<CNetObjPlayer*>(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<const CPed*>(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<CPed *>(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<CNetObjPlayer*>(pOwnerPed->GetNetworkObject());
if (pOwnerNetObjPlayer && pOwnerNetObjPlayer->IsPassiveMode())
{
bPassiveMode = true;
}
}
}
if (m_pStickEntity && m_pStickEntity->GetIsTypePed())
{
CPed* pStickyPed = static_cast<CPed *>(m_pStickEntity.Get());
if(pStickyPed->IsPlayer() && pStickyPed->GetNetworkObject())
{
CNetObjPlayer* pStickyNetObjPlayer = static_cast<CNetObjPlayer*>(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<const CPed*>(m_pStickEntity.Get());
if (pPed && pPed->GetIsInVehicle() && pPed->GetVehiclePedInside())
{
const CVehicle *pVehicle = static_cast<const CVehicle*>(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<CPhysical*>( 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<phBoundComposite*>( pFragInst->GetArchetype()->GetBound() );
if( pBoundComposite )
{
// If this vehicle part is smashed then don't try to attach
CVehicle* pVehicle = static_cast<CVehicle*>( 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<const CVehicle*>(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<CConvertibleRoofWindowInfo>())
{
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<CWeaponInfo>(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<rage::u16>(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<CWeaponInfo>(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<phBoundComposite*>(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<CVehicle*>(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<const phBoundComposite*>(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<CPed*>( 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<CPed*>(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<CVehicle*>(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<CPed *>(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<const CPed*>( m_pOwner.Get());
if (pOwnerPed)
{
const CWeaponInfo* pWeaponInfo = CWeaponInfoManager::GetInfo<CWeaponInfo>(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<CPed*>( 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<CPed*>( 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<CWeaponModelInfo*>(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<CPhysical&>(*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<CPed*>(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<const CTaskParachute*>(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<CVehicle*>(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<CVehicle*>(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<CPed*>(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<CPed*>(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<CEntity*>(pOwnerConst);
CPed* pPedOwner = static_cast<CPed*>(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<u8>(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<CWeaponInfo>(m_uWeaponFiredFromHash);
if (m_pOwner && m_pOwner->GetIsTypeVehicle() && pWeaponInfo && pWeaponInfo->GetIsVehicleWeapon())
{
const CVehicle* pVehicle = static_cast<CVehicle*>(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<const CDynamicEntity*>(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<const CPed*>(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<CEntity *>(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<CEntity*>(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<CEntity*>(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<CWeaponInfo>(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<CPed*>(m_pOwner.Get());
}
CVehicle* pOwningVehicle = NULL;
if(m_pOwner && m_pOwner->GetIsTypeVehicle())
{
pOwningVehicle = static_cast<CVehicle*>(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<CProjectileRocket*> 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<const CPed*>(pHitEntity);
// In vehicle: only stick to peds on bike/quadbike/jetski.
bool bInValidVehicle = true;
if (pHitPed->GetIsInVehicle())
{
const CVehicle *pVehicle = static_cast<CVehicle*>(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<const CObject*>(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<const CVehicle*>( 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<const CPhysical*>(pHitEntity);
const CEntity* pAttachedEntity = pHitPhysical->GetIsAttached() ? static_cast<const CEntity*>(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<phBoundComposite*>( 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<phMaterialMgr::Id>(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<CDynamicEntity*>(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<CDynamicEntity*>(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<CPhysical*>(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<CNetObjObject*>(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<CPed*>(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<CPhysical*>(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<CPed*>( 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<phBoundComposite*>(pFragInst->GetArchetype()->GetBound());
if (pBoundComposite)
{
CVehicle* pVehicle = static_cast<CVehicle*>(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<const CPhysical*>(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<const CVehicle*>(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<CWeaponInfo>(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<CPed*>(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<CPed *>(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<const CVehicle*>(pTrailerEntity);
if (pVehicle->InheritsFromTrailer())
{
const CTrailer* pTrailer = static_cast<const CTrailer*>(pVehicle);
const CVehicle* pAttachedVehicle = pTrailer->GetAttachedToParent();
if (pAttachedVehicle && pAttachedVehicle == pTargetEntity)
{
return true;
}
}
else if (pVehicle->GetAttachedTrailer())
{
const CTrailer* pTrailer = static_cast<const CTrailer*>(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<const CDynamicEntity*>(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<CDynamicEntity*>(m_pIgnoreDamageEntity.Get())) : m_pIgnoreDamageEntity.Get()->GetModelName(),m_pIgnoreDamageEntity.Get(),
pStickEntity && pStickEntity->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast<CDynamicEntity*>(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<CDynamicEntity*>(pStickEntity)) : pStickEntity->GetModelName(), pStickEntity,
m_pIgnoreDamageEntity.Get()->GetIsDynamic() ? AILogging::GetDynamicEntityNameSafe(static_cast<CDynamicEntity*>(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<CPed *>(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<CPhysical*>(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<CDynamicEntity*>(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<COUNTOF(vOffsets) ; i++)
{
WorldProbe::CShapeTestFixedResults<> 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<CPed*>(pEntityResponsible)->IsAPlayerPed() &&
static_cast<CPed*>(GetAttachParent())->IsAPlayerPed() )
{
const CPed *pVictimPlayer = static_cast<const CPed*>(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<CPed*>(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<CWeaponInfo>(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<const CAmmoRocketInfo*>(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<CPed*>(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<CVehicle*>(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<CNetObjPlayer*>(pTriggerVehicle->GetDriver()->GetNetworkObject());
if (pNearbyNetObjPlayer && pNearbyNetObjPlayer->IsPassiveMode())
{
continue;
}
}
}
if (!m_pInfo->GetProximityAffectsFiringPlayer() && m_pOwner && m_pOwner->GetIsTypePed())
{
const CPed* pPedOwner = static_cast<CPed*>(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<CPed*>(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<CPed*>(m_pOwner.Get());
if (pPedOwner->IsLocalPlayer())
{
continue;
}
}
// Ignore if player is ghosted / in passive mode
if (NetworkInterface::IsGameInProgress() && pTriggerPed->GetNetworkObject())
{
CNetObjPlayer* pNearbyNetObjPlayer = static_cast<CNetObjPlayer*>(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);
}
}
}
}