// File header #include "Peds/PedWeapons/PlayerPedTargeting.h" //rage headers #include "math/vecmath.h" #include "fwmaths/geomutil.h" #include "fwmaths/vectorutil.h" // Game headers #include "Camera/CamInterface.h" #include "Camera/Gameplay/GameplayDirector.h" #include "Camera/Gameplay/Aim/ThirdPersonAimCamera.h" #include "camera/gameplay/aim/FirstPersonShooterCamera.h" #include "Debug/DebugScene.h" #include "Game/Localisation.h" #include "Event/EventShocking.h" #include "Event/EventWeapon.h" #include "Event/ShockingEvents.h" #include "Frontend/NewHud.h" #include "Ik/solvers/TorsoSolver.h" #include "Network/Network.h" #include "PedGroup/PedGroup.h" #include "Peds/Ped_Channel.h" #include "Peds/PedCapsule.h" #include "Peds/PedIntelligence.h" #include "Peds/PedWeapons/PedTargetEvaluator.h" #include "Peds/Ped.h" #include "Physics/Physics.h" #include "Task/Combat/Cover/TaskCover.h" #include "Task/Combat/TaskThreatResponse.h" #include "Task/Default/TaskPlayer.h" #include "Task/Weapons/TaskProjectile.h" #include "Task/Weapons/Gun/TaskAimGunOnFoot.h" #include "Task/Weapons/Gun/TaskVehicleDriveBy.h" #include "Task/Motion/TaskMotionBase.h" #include "vehicleAi/VehicleIntelligence.h" #include "vehicles/heli.h" #include "Vehicles/VehicleGadgets.h" #include "Vehicles/Metadata/VehicleLayoutInfo.h" #include "Weapons/Weapon.h" #include "Weapons/WeaponDebug.h" // Parser headers #include "Peds/PedWeapons/PedWeapons_parser.h" AI_OPTIMISATIONS() extern const float g_DefaultDeadPedLockOnDuration = 0.75f; ////////////////////////////////////////////////////////////////////////// // CCurve ////////////////////////////////////////////////////////////////////////// CCurve::CCurve() : m_fInitialValue(0.0f) , m_fInputMin(0.0f) , m_fInputMax(0.0f) , m_fResultMax(0.0f) , m_fPow(0.0f) , m_fOneOverInputMaxMinusInputMin(0.0f) { } float CCurve::GetResult(float fInput) const { if(fInput <= m_fInputMax) { float fResult = 0.0f; // If input min and max are equal, just effectively return m_fInitialValue if(m_fInputMax != m_fInputMin) { fResult = rage::Powf((fInput - m_fInputMin) * m_fOneOverInputMaxMinusInputMin, m_fPow); } fResult = m_fInitialValue + fResult * Max((m_fResultMax - m_fInitialValue), 0.0f); fResult = Clamp(fResult, 0.0f, 1.0f); return fResult; } // No result for input return -1.0f; } void CCurve::UpdateCurveOnChange() { UpdateOneOverInputMaxMinusInputMinInternal(); CPlayerPedTargeting::FinaliseCurveSets(); } void CCurve::UpdateOneOverInputMaxMinusInputMinInternal() { m_fOneOverInputMaxMinusInputMin = 1.0f / (m_fInputMax - m_fInputMin); } ////////////////////////////////////////////////////////////////////////// // CCurveSet ////////////////////////////////////////////////////////////////////////// CCurveSet::CCurveSet() #if __BANK : m_colour(Color_white) #endif // __BANK { } void CCurveSet::SetCurve(s32 iCurveIndex, float fInputMax, float fResultMax, float fPow) { pedFatalAssertf(iCurveIndex >= 0 && iCurveIndex < m_curves.GetCount(), "iCurveIndex is out of array bounds"); m_curves[iCurveIndex].Set(fInputMax, fResultMax, fPow); } float CCurveSet::GetResult(float fInput) const { for(s32 i = 0; i < m_curves.GetCount(); i++) { float fCurveResult = m_curves[i].GetResult(fInput); if(fCurveResult >= 0.0f) { return fCurveResult; } } return 0.0f; } void CCurveSet::Finalise() { for(s32 i = 0; i < m_curves.GetCount(); i++) { if(i > 0) { if(m_curves[i].GetInputMax() < m_curves[i-1].GetInputMax()) { m_curves[i].SetInputMax(m_curves[i-1].GetInputMax()); } m_curves[i].SetInputMin(m_curves[i-1].GetInputMax()); } m_curves[i].UpdateOneOverInputMaxMinusInputMinInternal(); } for(s32 i = 1; i < m_curves.GetCount(); i++) { m_curves[i].SetInitialValue(m_curves[i-1].GetResult(m_curves[i-1].GetInputMax())); } } #if __BANK void CCurveSet::PreAddWidgets(bkBank& bank) { bank.PushGroup(m_Name.GetCStr(), false); } void CCurveSet::PostAddWidgets(bkBank& bank) { bank.PopGroup(); } void CCurveSet::Render(const Vector2& vOrigin, const Vector2& vEnd, float fInputScale) const { Vector2 vLastResult(vOrigin); float fInput = 0.0f; while(fInput <= fInputScale) { float fResult = GetResult(fInput); Vector2 vNewPoint; vNewPoint.x = vOrigin.x + (vEnd.x - vOrigin.x) * (fInput/fInputScale); vNewPoint.y = vOrigin.y + (vEnd.y - vOrigin.y) * fResult; grcDebugDraw::Line(vLastResult, vNewPoint, m_colour); static bank_float INCREMENT = 0.01f; fInput += (INCREMENT * fInputScale); vLastResult = vNewPoint; } } #endif // __BANK void CTargettingDifficultyInfo::PostLoad() { #if __BANK Color32 colours[CTargettingDifficultyInfo::CS_Max] = { Color_orange, Color_blue, Color_cyan, Color_magenta }; Assert(m_CurveSets.GetCount() > 0); for(s32 i = 0; i < CTargettingDifficultyInfo::CS_Max; i++) { m_CurveSets[i].SetColour(colours[i]); } #endif // __BANK } ////////////////////////////////////////////////////////////////////////// // CPlayerPedTargeting ////////////////////////////////////////////////////////////////////////// // Static member initialisation CPlayerPedTargeting::Tunables CPlayerPedTargeting::ms_Tunables; IMPLEMENT_PLAYER_TARGETTING_TUNABLES(CPlayerPedTargeting, 0xcb21697a); const u8 CPlayerPedTargeting::MAX_FREE_AIM_ASYNC_RESULTS = 32; CPlayerPedTargeting::ePlayerTargetLevel CPlayerPedTargeting::ms_ePlayerTargetLevel = CPlayerPedTargeting::PTL_TARGET_STRANGERS; #if __BANK bool CPlayerPedTargeting::ms_bEnableSnapShotBulletBend = false; float CPlayerPedTargeting::ms_fSnapShotRadiusMin = 0.5f; float CPlayerPedTargeting::ms_fSnapShotRadiusMax = 2.0f; #endif PARAM(enablecovervantagepointforlos, "[CPlayerPedTargeting] Use cover vantage point for LOS tests"); //////////////////////////////////////////////////////////////////////////////// const CTargettingDifficultyInfo& CPlayerPedTargeting::Tunables::GetTargettingInfo() const { bool bFirstPerson = FPS_MODE_SUPPORTED_ONLY(CGameWorld::FindLocalPlayer() ? CGameWorld::FindLocalPlayer()->IsFirstPersonShooterModeEnabledForPlayer(false) || CGameWorld::FindLocalPlayer()->IsInFirstPersonVehicleCamera():) false; switch(CPedTargetEvaluator::GetTargetMode(CGameWorld::FindLocalPlayer())) { case(CPedTargetEvaluator::TARGETING_OPTION_GTA_TRADITIONAL): return bFirstPerson ? m_FirstPersonEasyTargettingDifficultyInfo : m_EasyTargettingDifficultyInfo; case(CPedTargetEvaluator::TARGETING_OPTION_ASSISTED_AIM): return bFirstPerson ? m_FirstPersonNormalTargettingDifficultyInfo : m_NormalTargettingDifficultyInfo; case(CPedTargetEvaluator::TARGETING_OPTION_ASSISTED_FREEAIM): return bFirstPerson ? m_FirstPersonHardTargettingDifficultyInfo : m_HardTargettingDifficultyInfo; case(CPedTargetEvaluator::TARGETING_OPTION_FREEAIM): return bFirstPerson ? m_FirstPersonExpertTargettingDifficultyInfo : m_ExpertTargettingDifficultyInfo; } Assert(0); return m_EasyTargettingDifficultyInfo; } //////////////////////////////////////////////////////////////////////////////// CPlayerPedTargeting::CPlayerPedTargeting() : m_pPed(NULL) , m_FreeAimProbeResults(MAX_FREE_AIM_ASYNC_RESULTS) , m_FreeAimAssistProbeResults(MAX_FREE_AIM_ASYNC_RESULTS) , m_fLostLockLOSTimer(0.0f) , m_uLastLockLOSTestTime(0) , m_pPotentialTarget(NULL) , m_fPotentialTargetTimer(0.0f) , m_uManualSwitchTargetTime(0) { Reset(); } bool CPlayerPedTargeting::CanPromoteToHardLock(TargetingMode UNUSED_PARAM(eTargetingMode), const CWeapon* UNUSED_PARAM(pWeapon), const CWeaponInfo& UNUSED_PARAM(weaponInfo)) { return false; } void CPlayerPedTargeting::SetOwner(CPed* pPed) { m_pPed = pPed; Reset(); } void CPlayerPedTargeting::Reset() { m_fTimeAiming = 0.0f; m_pLockOnTarget =NULL; m_pCachedLockOnTarget = NULL; m_vLockOnTargetPos = Vector3::ZeroType; m_vLastLockOnTargetPos = Vector3::ZeroType; m_vClosestFreeAimTargetPos = Vector3::ZeroType; #if RSG_PC m_vClosestFreeAimTargetPosStereo = Vector3::ZeroType; m_vFreeAimTargetDir = Vector3::ZeroType; m_fClosestFreeAimTargetDist = 0.0f; #endif m_vFineAimOffset = Vector3::ZeroType; m_fTimeFineAiming = 0.0f; m_fTimeLockedOn = 0.0f; m_bTargetSwitchedToLockOnTarget = false; m_fFineAimHorizontalSpeedWeight = 0.0f; m_bPlayerIsFineAiming = false; m_bPlayerReleasedUpDownControlForFineAim = false; m_bPlayerPressingUpDownControlForFineAim = false; m_bHasAttemptedNonSpringFineAim = false; m_bPlayerHasStartedToAim = false; m_uTimeSinceSoftFiringAndNotAiming = 0; m_bHasUpdatedLockOnPos = false; m_bPlayerHasBrokenLockOn = false; m_bWasWithinFineAimLimits = false; m_bCheckForLeftTarget = false; m_bCheckForRightTarget = false; m_fTimeSoftLockedOn = 0.0f; m_fTimeTryingToBreakSoftLock = 0.0f; m_fAssistedAimDeadPedLockOnTimer = -1.0f; m_bVehicleHomingEnabled = true; m_bVehicleHomingForceDisabled = false; m_pFreeAimTarget = NULL; m_uStartTimeAimingAtFreeAimTarget = 0; m_pFreeAimTargetRagdoll = NULL; m_pFreeAimAssistTarget = NULL; m_vFreeAimAssistTargetPos = Vector3::ZeroType; m_vClosestFreeAimAssistTargetPos = Vector3::ZeroType; m_bFreeAimTargetingVehicleForCamera = false; m_fCurveSetBlend = 0.0f; m_fLastCameraInput = 0.0f; m_FreeAimProbeResults.Reset(); m_FreeAimAssistProbeResults.Reset(); m_fLockOnRangeOverride = -1.0f; m_fLostLockLOSTimer = 0.0f; m_uLastLockLOSTestTime = 0; m_bNoReticuleAimAssist = false; m_bBlockNoReticuleAimAssist = false; m_uTimeSinceLastAiming = 0; m_vLastValidStickInput.Zero(); m_LastTimeDesiredStickInputSet = 0; m_eLastTargetingMode = None; m_uLastWeaponHash = 0; m_bNoTargetingMode = false; m_bTargetingStartedInWeaponWheelHUD = false; m_uTimeLockOnSwitchExtensionExpires = 0; m_pLockOnSwitchExtensionTarget = NULL; m_bCheckForLockOnSwitchExtension = false; } void CPlayerPedTargeting::Process() { pedFatalAssertf(m_pPed, "m_pPed is NULL"); m_pCachedLockOnTarget = NULL; // If we are assisted aiming, set the flag if (!m_pPed->GetIsInVehicle()) { // If no reticule and trying to blind fire, give an aim assist. bool bRunAndGun = m_pPed->GetPlayerInfo()->IsRunAndGunning(); if(CanUseNoReticuleAimAssist()) { m_bNoReticuleAimAssist = true; m_bBlockNoReticuleAimAssist = true; } else if(!bRunAndGun) { m_bBlockNoReticuleAimAssist = false; } if(m_bNoReticuleAimAssist) { if(!m_pPed->GetPlayerInfo()->IsRunAndGunning() || m_fTimeAiming > ms_Tunables.GetMinNoReticuleAimTime()) { m_bNoReticuleAimAssist = false; } if(m_fTimeAiming > ms_Tunables.GetMinNoReticuleAimTime()) { m_bNoReticuleAimAssist = false; } } // Or when the weapon wheel is active as this changes the camera if(!CNewHud::IsShowingHUDMenu()) { if (m_pPed->GetPlayerInfo()->IsAssistedAiming()) { m_pPed->SetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON); } else if(!m_pPed->GetIsInCover()) { if(m_bNoReticuleAimAssist) { m_pPed->SetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON); m_pPed->SetPlayerResetFlag(CPlayerResetFlags::PRF_NO_RETICULE_AIM_ASSIST_ON); } else if (bRunAndGun) { m_pPed->SetPlayerResetFlag(CPlayerResetFlags::PRF_RUN_AND_GUN); } } } } #if __BANK if(CPedTargetEvaluator::DEBUG_LOCKON_TARGET_SCRORING_ENABLED) { #if DEBUG_DRAW CTask::ms_debugDraw.ClearAll(); #endif CPedTargetEvaluator::EnableDebugScoring(true); FindLockOnTarget(); CPedTargetEvaluator::EnableDebugScoring(false); } if(CPedTargetEvaluator::DEBUG_MELEE_TARGET_SCRORING_ENABLED) { #if DEBUG_DRAW CTask::ms_debugDraw.ClearAll(); #endif CPedTargetEvaluator::EnableDebugScoring(true); static dev_bool s_bDoMeleeAction = true; FindMeleeTarget(m_pPed, GetWeaponInfo(), s_bDoMeleeAction, true); CPedTargetEvaluator::EnableDebugScoring(false); } #endif m_bCheckForLeftTarget = false; m_bCheckForRightTarget = false; // If we aren't allowed to update targeting, clear the lock on target bool bClearLockOn = !ShouldUpdateTargeting(); // Otherwise update the appropriate aiming mode if (!bClearLockOn) { pedFatalAssertf(GetWeaponInfo(), "Weapon Info is NULL"); const CWeapon* pEquippedWeapon = m_pPed->GetWeaponManager()->GetEquippedWeapon(); const CWeaponInfo& weaponInfo = *GetWeaponInfo(); //! If equipped weapon doesn't match weapon info, NULL it out as they don't match. if(pEquippedWeapon && (weaponInfo.GetHash() != pEquippedWeapon->GetWeaponInfo()->GetHash())) { pEquippedWeapon = NULL; } TargetingMode eTargetingMode = GetTargetingMode(pEquippedWeapon, weaponInfo); u32 uWeaponHash = pEquippedWeapon ? pEquippedWeapon->GetWeaponHash() : m_pPed->GetWeaponManager()->GetSelectedWeaponHash(); //! Cache current weapon hash. It'll reset correctly when we release targeting controls. bool bCacheLastWeaponHash = m_eLastTargetingMode == None || m_bTargetingStartedInWeaponWheelHUD || m_pPed->GetMyVehicle(); if(bCacheLastWeaponHash) { if(uWeaponHash != m_uLastWeaponHash) { m_uLastWeaponHash = uWeaponHash; } } if(CPlayerInfo::IsSoftAiming() || CPlayerInfo::IsHardAiming() || CPlayerInfo::IsDriverFiring() || (eTargetingMode != None) ) { if( (uWeaponHash != m_uLastWeaponHash) || m_bNoTargetingMode) { if(weaponInfo.GetCanFreeAim()) { m_pPed->GetPlayerInfo()->SetPlayerDataFreeAiming(true); if (!weaponInfo.GetIsOnFootHoming()) { eTargetingMode = FreeAim; } } else { eTargetingMode = None; } m_bNoTargetingMode = true; } } else { m_bNoTargetingMode = false; m_bTargetingStartedInWeaponWheelHUD = false; } // DMKH. Check if we can promote to hard lock mode. if(CanPromoteToHardLock(eTargetingMode, pEquippedWeapon, weaponInfo)) { eTargetingMode = HardLockOn; } // Update the time aiming variable if (m_pLockOnTarget || m_pPed->GetPlayerInfo()->GetPlayerDataFreeAiming()) { m_fTimeAiming += fwTimer::GetTimeStep(); //! If we have been aiming for min aim time (i.e. so we can move crosshair), flag last aim time. if( (m_fTimeAiming > ms_Tunables.GetMinFineAimTime())) { m_uTimeSinceLastAiming = fwTimer::GetTimeInMilliseconds(); } } else { const camGameplayDirector& gameplayDirector = camInterface::GetGameplayDirector(); bool bShowReticule = gameplayDirector.ShouldDisplayReticule() || CNewHud::IsReticuleVisible(); if(bShowReticule) { m_uTimeSinceLastAiming = fwTimer::GetTimeInMilliseconds(); } m_fTimeAiming = 0.0f; } // Update the free aim status m_pPed->GetPlayerInfo()->SetPlayerDataFreeAiming(eTargetingMode == FreeAim ? true : false); switch (eTargetingMode) { case HardLockOn: { bClearLockOn = !UpdateHardLockOn(pEquippedWeapon, weaponInfo); UpdateFreeAim(weaponInfo); } break; case SoftLockOn: { if (m_fTimeSoftLockedOn == 0.0f) { m_bWasWithinFineAimLimits = false; } bClearLockOn = !UpdateSoftLockOn(pEquippedWeapon, weaponInfo); if (bClearLockOn && m_pLockOnTarget) { // When initially clearing lockon, make it the freeaim target // (Can't just update free aim as probes are asynchronous) m_pFreeAimTarget = m_pLockOnTarget; m_pFreeAimTargetRagdoll = m_pLockOnTarget; } //try to set a free aim target if aiming and not locking UpdateFreeAim(weaponInfo); } break; case FreeAim: { bClearLockOn = !UpdateFreeAim(weaponInfo); } break; case None: { if(CNewHud::IsReticuleVisible()) { bClearLockOn = !UpdateFreeAim(weaponInfo); } else { bClearLockOn = true; // Clear Free aim targets when not aiming ClearFreeAim(); } } break; case Manual: if (weaponInfo.GetIsOnFootHoming() && !weaponInfo.GetIsVehicleWeapon()) { // Don't need to set bClearLockOn flag here as we handle clearing the lock on target within the ProcessOnFootHomingLockOn function. ProcessOnFootHomingLockOn(m_pPed); // B*2165686: Only update the free aim target if we're aiming (prevents us from triggering ped reaction events when we aren't aiming). if (CPlayerInfo::IsAiming()) { // B*2154594: Try to set free aim target if possible to ensure we get ped reaction events UpdateFreeAim(weaponInfo); } else { // BlairT: Clear free aim target/pos so we don't get stuck with stale data. ClearFreeAim(); m_vClosestFreeAimTargetPos = Vector3::ZeroType; } } else { // BlairT: Clear free aim target/pos so we don't get stuck with stale data. ClearFreeAim(); m_vClosestFreeAimTargetPos = Vector3::ZeroType; } break; default: break; } //! Have we started to target when in weapon wheel? if(m_eLastTargetingMode == None && (eTargetingMode != None) && CNewHud::IsShowingHUDMenu()) { m_bTargetingStartedInWeaponWheelHUD = true; } //! Cache targeting mode m_eLastTargetingMode = eTargetingMode; CControl* pControl = const_cast(m_pPed->GetControlFromPlayer()); if( pControl ) { // Get desired stick direction for melee lock on. const Vector2 vStickInput(pControl->GetPedWalkLeftRight().GetNorm(), -pControl->GetPedWalkUpDown().GetNorm()); if( vStickInput.Mag2() > rage::square( 0.5f ) ) { m_LastTimeDesiredStickInputSet = fwTimer::GetTimeInMilliseconds(); m_vLastValidStickInput = vStickInput; } } } else { m_fTimeAiming = 0.0f; } if (bClearLockOn) { ClearLockOnTarget(); } if(m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_NO_RETICULE_AIM_ASSIST_ON) && !m_pLockOnTarget ) { m_pPed->ClearPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON); m_pPed->ClearPlayerResetFlag(CPlayerResetFlags::PRF_NO_RETICULE_AIM_ASSIST_ON); }; // Send gun aimed at events to targeted peds ProcessPedEvents(); m_fLockOnRangeOverride = -1.0f; // Clear the weapon range override - needs to be set every frame from script #if __BANK TUNE_GROUP_BOOL(PLAYER_TARGETING, bDebugBulletBending, false); if (bDebugBulletBending) { const CPedWeaponManager* pWeaponManager = m_pPed->GetWeaponManager(); if (pWeaponManager) { const CWeapon* pWeapon = pWeaponManager->GetEquippedWeapon(); if (pWeapon && pWeapon->GetWeaponInfo() && pWeapon->GetWeaponInfo()->GetIsGunOrCanBeFiredLikeGun()) { const CObject* pWeaponObject = pWeaponManager->GetEquippedWeaponObject(); if (pWeaponObject) { const bool bCachedDebugTargettingValue = CPedTargetEvaluator::ms_Tunables.m_DebugTargetting; CPedTargetEvaluator::ms_Tunables.m_DebugTargetting = true; GetIsFreeAimAssistTargetPosWithinConstraints(pWeapon, RCC_MATRIX34(pWeaponObject->GetMatrixRef()), CPlayerPedTargeting::IsCameraInAccurateMode(), FIRING_VEC_CALC_FROM_AIM_CAM); CPedTargetEvaluator::ms_Tunables.m_DebugTargetting = bCachedDebugTargettingValue; } } } } #endif // __BANK } void CPlayerPedTargeting::ProcessPreCamera() { if(m_pPed && m_pPed->IsLocalPlayer()) { UpdateLockOnTargetPos(); } } const CEntity* CPlayerPedTargeting::GetTarget() const { if(m_pLockOnTarget) { // The lock-on target is definitive return m_pLockOnTarget; } else if(m_pFreeAimTarget) { return m_pFreeAimTarget; } else if(m_pFreeAimAssistTarget) { return m_pFreeAimAssistTarget; } return NULL; } CEntity* CPlayerPedTargeting::GetTarget() { if(m_pLockOnTarget) { // The lock-on target is definitive return m_pLockOnTarget; } else if(m_pFreeAimTarget) { return m_pFreeAimTarget; } else if(m_pFreeAimAssistTarget) { return m_pFreeAimAssistTarget; } return NULL; } void CPlayerPedTargeting::SetLockOnTarget(CEntity* pTarget, bool bTargetSwitched) { m_pLockOnTarget = pTarget; m_bTargetSwitchedToLockOnTarget = bTargetSwitched; if(m_pLockOnTarget) { m_pLockOnTarget->GetLockOnTargetAimAtPos(m_vLastLockOnTargetPos); #if USE_TARGET_SEQUENCES TUNE_GROUP_BOOL(PLAYER_TARGETING, ASSISTED_AIMING_USE_TARGET_SEQUENCES, true); // Update the lock-on position atHashWithStringNotFinal targetSequenceGroup = GetTargetSequenceGroup(); if ( ASSISTED_AIMING_USE_TARGET_SEQUENCES && m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON) && targetSequenceGroup.GetHash() && m_pLockOnTarget->GetIsTypePed()) { // TODO - pick a sequence group based on the weapon type (probably from weapon metadata) m_TargetSequenceHelper.InitSequence(targetSequenceGroup, m_pPed, static_cast(m_pLockOnTarget.Get())); } else if (m_pPed->GetIsDrivingVehicle() || !m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON)) #else // USE_TARGET_SEQUENCES //if (m_pPed->GetIsDrivingVehicle() || !m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON)) #endif // USE_TARGET_SEQUENCES { m_vLockOnTargetPos = m_vLastLockOnTargetPos; } } else { // No target, clear lock-on position m_vLockOnTargetPos.Zero(); m_vLastLockOnTargetPos.Zero(); TARGET_SEQUENCE_ONLY(m_TargetSequenceHelper.Reset()); m_bHasUpdatedLockOnPos = false; } m_fTimeLockedOn = 0.0f; // Clear the fine aim variables ClearFineAim(); } void CPlayerPedTargeting::ClearLockOnTarget() { pedFatalAssertf(m_pPed, "m_pPed is NULL"); if(m_pPed->IsControlledByLocalPlayer()) { m_pLockOnTarget = NULL; m_vLockOnTargetPos.Zero(); m_vLastLockOnTargetPos.Zero(); m_bHasUpdatedLockOnPos = false; TARGET_SEQUENCE_ONLY(m_TargetSequenceHelper.Reset();) m_fLostLockLOSTimer = 0.0f; m_uLastLockLOSTestTime = 0; m_bNoReticuleAimAssist = false; m_fTimeLockedOn = 0.0f; m_bTargetSwitchedToLockOnTarget = false; } } bool CPlayerPedTargeting::GetFineAimingOffset(Vector3& vFineAimOffset) const { vFineAimOffset = camInterface::GetPlayerControlCamAimFrame().GetRight() * m_vFineAimOffset.x; vFineAimOffset.z = m_vFineAimOffset.z; return m_bPlayerIsFineAiming; } #if !__FINAL PARAM(enableCNCFreeAimImprovements, "[CPlayerPedTargeting] Enable CNC free aim improvements."); PARAM(forceEnableCNCFreeAimImprovements, "[CPlayerPedTargeting] Force enable CNC free aim improvements (even if not in the mode)."); #endif // !__FINAL bool CPlayerPedTargeting::GetAreCNCFreeAimImprovementsEnabled(const CPed* CNC_MODE_ENABLED_ONLY(pPed), const bool CNC_MODE_ENABLED_ONLY(bControllerOnly)) { #if CNC_MODE_ENABLED if (pPed) { const CPlayerInfo* pPlayerInfo = pPed->GetPlayerInfo(); if (pPlayerInfo) { return pPlayerInfo->GetTargeting().GetAreCNCFreeAimImprovementsEnabled(bControllerOnly); } } #endif // CNC_MODE_ENABLED return false; } bool CPlayerPedTargeting::GetAreCNCFreeAimImprovementsEnabled(const bool CNC_MODE_ENABLED_ONLY(bControllerOnly)) const { #if CNC_MODE_ENABLED #if !__FINAL TUNE_GROUP_BOOL(PLAYER_TARGETING, bForceCnCFreeAimImprovements, false); if (bForceCnCFreeAimImprovements || PARAM_forceEnableCNCFreeAimImprovements.Get()) { return true; } #endif // !__FINAL TUNE_GROUP_BOOL(PLAYER_TARGETING, bEnableCnCFreeAimImprovements, true); if ((bEnableCnCFreeAimImprovements || PARAM_enableCNCFreeAimImprovements.Get()) && NetworkInterface::IsInCopsAndCrooks()) { if (bControllerOnly) { #if RSG_PC const CControl* pControl = m_pPed->GetControlFromPlayer(); if(pControl && pControl->UseRelativeLook()) { return false; } #endif // RSG_PC } const u32 uTargetMode = CPedTargetEvaluator::GetTargetMode(m_pPed); if (uTargetMode == CPedTargetEvaluator::TARGETING_OPTION_FREEAIM || uTargetMode == CPedTargetEvaluator::TARGETING_OPTION_ASSISTED_FREEAIM) { return true; } } #endif // CNC_MODE_ENABLED return false; } bool CPlayerPedTargeting::GetIsFreeAimAssistTargetPosWithinConstraints(const CWeapon* pWeapon, const Matrix34& mWeapon, bool bZoomed, eFiringVecCalcType eFireVecType) const { pedFatalAssertf(m_pPed, "m_pPed is NULL"); if(m_pFreeAimAssistTarget) { if(pWeapon) { Vector3 vStart, vEnd; switch (eFireVecType) { // TODO: Fix up the parameters for unused types case FIRING_VEC_CALC_FROM_POS: pWeapon->CalcFireVecFromPos(m_pPed, mWeapon, vStart, vEnd, VEC3V_TO_VECTOR3(m_pFreeAimAssistTarget->GetTransform().GetPosition())); break; case FIRING_VEC_CALC_FROM_ENT: pWeapon->CalcFireVecFromEnt(m_pPed, mWeapon, vStart, vEnd, m_pFreeAimAssistTarget); break; case FIRING_VEC_CALC_FROM_AIM_CAM: pWeapon->CalcFireVecFromAimCamera(m_pPed->GetMyVehicle() != NULL ? static_cast(m_pPed->GetMyVehicle()) : m_pPed, mWeapon, vStart, vEnd); break; case FIRING_VEC_CALC_FROM_GUN_ORIENTATION: pWeapon->CalcFireVecFromGunOrientation(m_pPed, mWeapon, vStart, vEnd); break; case FIRING_VEC_CALC_FROM_WEAPON_MATRIX: { // Hack to use bullet bending on jetpack lasers pWeapon->CalcFireVecFromWeaponMatrix(mWeapon, vStart, vEnd); } break; default: taskAssertf(0,"Unhandled firing vector calc type"); } return GetIsFreeAimAssistTargetPosWithinConstraints(pWeapon, vStart, vEnd, bZoomed); } } return false; } bool CPlayerPedTargeting::GetIsFreeAimAssistTargetPosWithinConstraints(const CWeapon* pWeapon, const Vector3& vStart, Vector3& vEnd, bool bZoomed) const { pedFatalAssertf(m_pPed, "m_pPed is NULL"); if(m_pFreeAimAssistTarget) { if(pWeapon) { float fNearRadius = 0.0f; float fFarRadius = 0.0f; GetBulletBendingData(m_pPed, pWeapon, bZoomed, fNearRadius, fFarRadius); fwGeom::fwLine line(vStart, vEnd); Vector3 vClosestPoint; float fT = line.FindClosestPointOnLine(m_vFreeAimAssistTargetPos, vClosestPoint); float fRadiusAtDistance = fNearRadius + fT * (fFarRadius - fNearRadius); // If distance from closest point on aiming vector to free aim assist target position is within the radius bool bWithinConstraints = m_vFreeAimAssistTargetPos.Dist2(vClosestPoint) < rage::square(fRadiusAtDistance); #if __BANK RenderAimAssistConstraints(bWithinConstraints, vStart, vEnd, vClosestPoint, fRadiusAtDistance); #endif // __BANK return bWithinConstraints; } } return false; } bool CPlayerPedTargeting::GetIsFreeAimAssistTargetPosWithinReticuleSlowDownConstraints(const CWeapon* pWeapon, const Vector3& vStart, Vector3& vEnd, bool bZoomed) const { if (ms_Tunables.GetUseCapsuleTests()) { return GetIsFreeAimAssistTargetPosWithinReticuleSlowDownConstraintsNew(pWeapon, vStart, vEnd, bZoomed); } if(m_pFreeAimAssistTarget) { if(pWeapon) { #if __BANK float fNearRadius = 0.0f; float fFarRadius = 0.0f; GetBulletBendingData(m_pPed, pWeapon, bZoomed, fNearRadius, fFarRadius); fwGeom::fwLine line(vStart, vEnd); Vector3 vClosestPoint; float fT = line.FindClosestPointOnLine(m_vFreeAimAssistTargetPos, vClosestPoint); float fRadiusAtDistance = fNearRadius + fT * (fFarRadius - fNearRadius); // If distance from closest point on aiming vector to free aim assist target position is within the radius bool bWithinConstraints = m_vFreeAimAssistTargetPos.Dist2(vClosestPoint) < rage::square(fRadiusAtDistance); RenderAimAssistConstraints(bWithinConstraints, vStart, vEnd, vClosestPoint, fRadiusAtDistance); #endif // __BANK Vector3 vOne = vStart; Vector3 vTwo = vEnd; const camFrame& aimFrame = camInterface::GetPlayerControlCamAimFrame(); Matrix34 mFreeAimAssistMtx = aimFrame.GetWorldMatrix(); mFreeAimAssistMtx.d = VEC3V_TO_VECTOR3(m_pFreeAimAssistTarget->GetTransform().GetPosition()); mFreeAimAssistMtx.UnTransform(vOne); mFreeAimAssistMtx.UnTransform(vTwo); Vector3 vOneToTwo = vTwo - vOne; float fUnused = 0.0f; if (geomSegments::SegmentToDiskIntersection(vOne, vOneToTwo, ms_Tunables.GetReticuleSlowDownRadius(), &fUnused)) { return true; } } } return false; } bool CPlayerPedTargeting::GetIsFreeAimAssistTargetPosWithinReticuleSlowDownConstraintsNew(const CWeapon* pWeapon, const Vector3& vStart, Vector3& vEnd, bool BANK_ONLY(bZoomed)) const { if(m_pFreeAimAssistTarget) { if(pWeapon) { #if __BANK float fNearRadius = 0.0f; float fFarRadius = 0.0f; GetBulletBendingData(m_pPed, pWeapon, bZoomed, fNearRadius, fFarRadius); fwGeom::fwLine line(vStart, vEnd); Vector3 vClosestPoint; float fT = line.FindClosestPointOnLine(m_vFreeAimAssistTargetPos, vClosestPoint); float fRadiusAtDistance = fNearRadius + fT * (fFarRadius - fNearRadius); // If distance from closest point on aiming vector to free aim assist target position is within the radius bool bWithinConstraints = m_vFreeAimAssistTargetPos.Dist2(vClosestPoint) < rage::square(fRadiusAtDistance); RenderAimAssistConstraints(bWithinConstraints, vStart, vEnd, vClosestPoint, fRadiusAtDistance); #endif // __BANK Vector3 vOne = vStart; Vector3 vTwo = vEnd; const camFrame& aimFrame = camInterface::GetPlayerControlCamAimFrame(); Matrix34 mFreeAimAssistMtx = aimFrame.GetWorldMatrix(); mFreeAimAssistMtx.d = VEC3V_TO_VECTOR3(m_pFreeAimAssistTarget->GetTransform().GetPosition()); mFreeAimAssistMtx.UnTransform(vOne); mFreeAimAssistMtx.UnTransform(vTwo); // geomSegments::SegmentCapsuleIntersectDirected assumes the capsule is lying on the y axis (length) so we need to rotate the aim vector accordingly static float ROTATE_X = HALF_PI; vOne.RotateX(ROTATE_X); vTwo.RotateX(ROTATE_X); float fLength = ms_Tunables.GetReticuleSlowDownCapsuleLength(); float fRadius = ms_Tunables.GetReticuleSlowDownCapsuleRadius(); /*if(m_fCurveSetBlend > 0.0f) { static dev_float s_Scale = 1.2f; fLength *= s_Scale; fRadius *= s_Scale; }*/ Vector3 vOneToTwo = vTwo - vOne; Vec3V vPositionOnCapsule; Vec3V vNormalOnCapsule; ScalarV scFractionAlongSegment; bool bIntersectionFound = geomSegments::SegmentCapsuleIntersectDirected(RCC_VEC3V(vOne), RCC_VEC3V(vOneToTwo), ScalarVFromF32(fLength), ScalarVFromF32(fRadius), vPositionOnCapsule, vNormalOnCapsule, scFractionAlongSegment).Getb(); if (bIntersectionFound) { return true; } } } return false; } #if __BANK void CPlayerPedTargeting::RenderAimAssistConstraints(bool bWithinConstraints, const Vector3& vStart, const Vector3& vEnd, const Vector3& vClosestPoint, float fRadiusAtDistance) const { if(CPedTargetEvaluator::ms_Tunables.m_DebugTargetting) { Color32 debugColour = bWithinConstraints ? Color_green : Color_red; static const u32 uDebugPartialHash = atPartialStringHash("AIM_ASSIST_CIRCLE_"); CWeaponDebug::ms_debugStore.AddLine(RCC_VEC3V(vStart), RCC_VEC3V(vEnd), debugColour, 1000, atStringHash("TargetLine", uDebugPartialHash)); Vec3V vShotDir(vEnd - vStart); vShotDir = NormalizeFast(vShotDir); Vec3V vRight, vUp; vRight = CrossZAxis(vShotDir); vRight = NormalizeFast(vRight); vUp = Cross(vRight, vShotDir); vUp = NormalizeFast(vUp); const s32 NUM_POINTS = 12; float fAngleIncrement = 2.0f * PI / NUM_POINTS; float fRotAngle = 0; ScalarV fRadiusAtDistanceV = ScalarVFromF32(fRadiusAtDistance); for(s32 i = 1; i <= NUM_POINTS; i++) { Vec3V a(vClosestPoint); a += fRadiusAtDistanceV * vRight * ScalarVFromF32(rage::Sinf(fRotAngle)); a += fRadiusAtDistanceV * vUp * ScalarVFromF32(rage::Cosf(fRotAngle)); fRotAngle = fAngleIncrement * i; Vec3V b(vClosestPoint); b += fRadiusAtDistanceV * vRight * ScalarVFromF32(rage::Sinf(fRotAngle)); b += fRadiusAtDistanceV * vUp * ScalarVFromF32(rage::Cosf(fRotAngle)); char buff[3]; sprintf(buff, "%d", i); CWeaponDebug::ms_debugStore.AddLine(a, b, debugColour, 1000, atStringHash(buff, uDebugPartialHash)); } } } #endif // __BANK bool CPlayerPedTargeting::GetScaledLookAroundInputMagForCamera(const float fCurrentLookAroundInputMag, float& fScaledLookAroundInputMag) { pedFatalAssertf(m_pPed, "m_pPed is NULL"); if (!ms_Tunables.GetUseReticuleSlowDown()) { return false; } if (m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_RUN_AND_GUN) && !ms_Tunables.GetUseReticuleSlowDownForRunAndGun()) { return false; } bool bInAccurateMode = IsCameraInAccurateMode(); bool bValidCam = false; if(camInterface::GetGameplayDirector().GetThirdPersonAimCamera() FPS_MODE_SUPPORTED_ONLY(|| camInterface::GetGameplayDirector().GetFirstPersonShooterCamera()) ) { bValidCam = true; } if(bValidCam) { weaponAssert(m_pPed->GetWeaponManager()); const CWeapon* pWeapon = m_pPed->GetWeaponManager()->GetEquippedWeapon(); if(pWeapon && pWeapon->GetWeaponInfo()->GetIsGunOrCanBeFiredLikeGun()) { CObject* pObject = m_pPed->GetWeaponManager()->GetEquippedWeaponObject(); if (pObject) { const Matrix34& mWeapon = RCC_MATRIX34(pObject->GetMatrixRef()); if (ms_Tunables.GetUseNewSlowDownCode()) { Vector3 vStart(Vector3::ZeroType); Vector3 vEnd(Vector3::ZeroType); if (m_pFreeAimAssistTarget && pWeapon->CalcFireVecFromAimCamera(m_pPed->GetMyVehicle() != NULL ? static_cast(m_pPed->GetMyVehicle()) : m_pPed, mWeapon, vStart, vEnd) && GetIsFreeAimAssistTargetPosWithinReticuleSlowDownConstraints(pWeapon, vStart, vEnd, bInAccurateMode)) { m_fCurveSetBlend += fwTimer::GetTimeStep() * (1.0f/ms_Tunables.GetAimAssistBlendInTime()); } else { m_fCurveSetBlend -= fwTimer::GetTimeStep() * (1.0f/ms_Tunables.GetAimAssistBlendOutTime()); } } else { if((m_pFreeAimAssistTarget && GetIsFreeAimAssistTargetPosWithinConstraints(pWeapon, mWeapon, bInAccurateMode, FIRING_VEC_CALC_FROM_AIM_CAM)) || (ms_Tunables.m_UseRagdollTargetIfNoAssistTarget && m_pFreeAimTarget)) { m_fCurveSetBlend += fwTimer::GetTimeStep() * (1.0f/ms_Tunables.GetAimAssistBlendInTime()); } else { m_fCurveSetBlend -= fwTimer::GetTimeStep() * (1.0f/ms_Tunables.GetAimAssistBlendOutTime()); } } m_fCurveSetBlend = Clamp(m_fCurveSetBlend, 0.0f, 1.0f); //! If we are strafing, need to clamp our scaled input so that it doesn't get to low as the reticule can "bounce" //! off our free aim assist target. Vector2 vMoveBlendRatio; m_pPed->GetMotionData()->GetCurrentMoveBlendRatio(vMoveBlendRatio); float fAimAssistDistanceResult = 1.0f; float fMinScaleInput = 0.0f; float fBlend = m_fCurveSetBlend; if(abs(vMoveBlendRatio.x) > 0.0f) { vMoveBlendRatio.NormalizeSafe(); float fStrafeScale = abs(vMoveBlendRatio.x); if(m_pFreeAimAssistTarget) { Vector3 vLockOnPos = VEC3V_TO_VECTOR3(m_pFreeAimAssistTarget->GetTransform().GetPosition()); Vector3 vPedPos = VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition()); Vector3 vDistance = vLockOnPos - vPedPos; float fDistanceToFreeAimAssistTarget = vDistance.XYMag(); fAimAssistDistanceResult = fStrafeScale * GetAimDistanceScaleCurve()->GetResult(fDistanceToFreeAimAssistTarget); if(ms_Tunables.m_UseReticuleSlowDownStrafeClamp) { fMinScaleInput = fAimAssistDistanceResult; } else { fBlend *= fAimAssistDistanceResult; } } } if(bInAccurateMode) { float fAimZoomValue = GetCurveSet(CTargettingDifficultyInfo::CS_AimZoom)->GetResult(fCurrentLookAroundInputMag); float fAimAssistZoomValue = GetCurveSet(CTargettingDifficultyInfo::CS_AimAssistZoom)->GetResult(fCurrentLookAroundInputMag); fScaledLookAroundInputMag = Lerp(fBlend, fAimZoomValue, fAimAssistZoomValue); } else { float fAimValue = GetCurveSet(CTargettingDifficultyInfo::CS_Aim)->GetResult(fCurrentLookAroundInputMag); float fAimAssistValue = GetCurveSet(CTargettingDifficultyInfo::CS_AimAssist)->GetResult(fCurrentLookAroundInputMag); fScaledLookAroundInputMag = Lerp(fBlend, fAimValue, fAimAssistValue); } if(ms_Tunables.m_UseReticuleSlowDownStrafeClamp && (fScaledLookAroundInputMag < fMinScaleInput)) { fScaledLookAroundInputMag = Min(fMinScaleInput, fCurrentLookAroundInputMag); } //! Ensure we haven't increased look around input. taskAssert(fScaledLookAroundInputMag <= fCurrentLookAroundInputMag); m_fLastCameraInput = fCurrentLookAroundInputMag; return true; } } } return false; } #if __BANK // void CPlayerPedTargeting::AddWidgets(bkBank& bank)) // { // bank.PushGroup("SNAPSHOT", false); // bank.AddToggle("Enable bullet bend", &CPlayerPedTargeting::ms_bEnableSnapShotBulletBend); // bank.AddSlider("Min radius bullet bend influence", & CPlayerPedTargeting::ms_fSnapShotRadiusMin, 0.0f, 20.0f, 0.1f); // bank.AddSlider("Max radius bullet bend influence", & CPlayerPedTargeting::ms_fSnapShotRadiusMax, 0.0f, 20.0f, 0.1f); // bank.PopGroup(); // } void CPlayerPedTargeting::Tunables::SetEntityTargetableDistCB() { CEntity* pFocusEntity = CDebugScene::FocusEntities_Get(0); if (pFocusEntity) { pFocusEntity->SetLockOnTargetableDistance(ms_Tunables.m_fTargetableDistance); } } void CPlayerPedTargeting::Tunables::SetEntityTargetThreatCB() { CEntity* pFocusEntity = CDebugScene::FocusEntities_Get(0); if (pFocusEntity && pFocusEntity->GetIsTypePed()) { ((CPed*)pFocusEntity)->SetTargetThreatOverride(ms_Tunables.m_fTargetThreatOverride); } } void CPlayerPedTargeting::Debug() { if ( ms_Tunables.m_DisplayFreeAimTargetDebug ) { grcDebugDraw::Sphere(m_vClosestFreeAimTargetPos, 0.1f, Color_blue); Vector3 vTargeterPosition = VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition()); grcDebugDraw::Line(vTargeterPosition, m_vClosestFreeAimTargetPos, Color_green); } if( ms_Tunables.m_DisplaySoftLockDebug && (ms_Tunables.GetLockType() == LT_Soft) ) { grcDebugDraw::AddDebugOutput(Color_green, "Time Soft Locked : %.2f", m_fTimeSoftLockedOn); if (m_bPlayerHasStartedToAim) grcDebugDraw::AddDebugOutput(Color_green, "Started Soft Aiming : true"); else grcDebugDraw::AddDebugOutput(Color_green, "Started Soft Aiming : false"); if (m_bPlayerHasBrokenLockOn) grcDebugDraw::AddDebugOutput(Color_green, "Broken Lock On : true"); else grcDebugDraw::AddDebugOutput(Color_green, "Broken Lock On : false"); const CControl* pControl = m_pPed->GetControlFromPlayer(); if (pControl) { Vector2 vecStickInput; // * 128.0f to convert to old range. We could have just printed text but it is in a different range from other relevent text. vecStickInput.x = pControl->GetPedAimWeaponLeftRight().GetNorm() * 128.0f; vecStickInput.y = pControl->GetPedAimWeaponUpDown().GetNorm() * 128.0f; grcDebugDraw::AddDebugOutput(Color_green, "X: %.2f, Y: %2f.", vecStickInput.x, vecStickInput.y); } Vector3 vFineAim; GetFineAimingOffset(vFineAim); grcDebugDraw::AddDebugOutput(Color_green, "Fine Aim Offset: (%.2f,%.2f,%.2f)", vFineAim.x, vFineAim.y, vFineAim.z); } if(ms_Tunables.m_DebugLockOnTargets) { const CWeaponInfo* pWeaponInfo = GetWeaponInfo(); if(pWeaponInfo && pWeaponInfo->GetIsGunOrCanBeFiredLikeGun()) { Vector3 vTargeterPosition = VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition()); const float fWeaponRange = GetWeaponLockOnRange(pWeaponInfo, m_pLockOnTarget); const float fLockOnRange = fWeaponRange * ms_Tunables.GetLockOnRangeModifier(); const CPedScanner& pedScanner = *(m_pPed->GetPedIntelligence()->GetPedScanner()); const CEntityScannerIterator entityList = pedScanner.GetIterator(); for (const CEntity* pEnt = static_cast(entityList.GetFirst()); pEnt; pEnt = entityList.GetNext()) { const CPed* pNearbyPed = static_cast(pEnt); if (pNearbyPed) { Vector3 vTargetPosition = VEC3V_TO_VECTOR3(pNearbyPed->GetTransform().GetPosition()); pNearbyPed->GetLockOnTargetAimAtPos(vTargetPosition); Vector3 vDelta = vTargetPosition - vTargeterPosition; float fDistanceSq = vDelta.Mag2(); grcDebugDraw::Line(vTargetPosition, vTargeterPosition, fDistanceSq > rage::square(fLockOnRange) ? Color_red : Color_green); vTargetPosition.z += 1.0f; grcDebugDraw::Sphere(vTargetPosition, 0.05f, fDistanceSq > rage::square(fLockOnRange) ? Color_red : Color_green); CPedTargetEvaluator::tHeadingLimitData headingData; headingData.fHeadingAngularLimit = ms_Tunables.GetDefaultTargetAngularLimit(); headingData.fHeadingAngularLimitClose = ms_Tunables.GetDefaultTargetAngularLimitClose(); headingData.fHeadingAngularLimitCloseDistMin = ms_Tunables.GetDefaultTargetAngularLimitCloseDistMin(); headingData.fHeadingAngularLimitCloseDistMax = ms_Tunables.GetDefaultTargetAngularLimitCloseDistMax(); const float fMaxHeadingDifference = (DtoR * headingData.GetAngularLimitForDistance(rage::Sqrtf(fDistanceSq))); Vector3 vHeading = camInterface::GetPlayerControlCamAimFrame().GetFront(); vDelta.Normalize(); const float fOrientation = rage::Atan2f(-vDelta.x, vDelta.y); const float fOrientationDiff = fOrientation - rage::Atan2f( -vHeading.x, vHeading.y ); vTargetPosition.z += 0.1f; grcDebugDraw::Sphere(vTargetPosition, 0.05f, Abs(fOrientationDiff) > fMaxHeadingDifference ? Color_red : Color_green); Vector3 vLockonTargetPos = GetLockonTargetPos(); vLockonTargetPos.z += 1.5f; grcDebugDraw::Sphere(vLockonTargetPos, 0.05f, Color_blue); } } } } if(CPedTargetEvaluator::ms_Tunables.m_DebugTargetting) { // Call the lock-on search code in order to display the debug //GetLockOnTarget(); // If GetLockOnTarget returns a CEntity then no need to clear it here //ClearLockOnTarget(); if(!ms_Tunables.m_DisplayAimAssistIntersections) { if(m_pFreeAimTarget) { grcDebugDraw::Sphere(VEC3V_TO_VECTOR3(m_pFreeAimTarget->GetTransform().GetPosition()), 1.0f, Color_red, false, -1); } if(m_pFreeAimTargetRagdoll) { grcDebugDraw::Sphere(VEC3V_TO_VECTOR3(m_pFreeAimTargetRagdoll->GetTransform().GetPosition()), 1.0f, Color_blue, false, -1); } if(m_pFreeAimAssistTarget) { grcDebugDraw::Sphere(m_vFreeAimAssistTargetPos, 0.1f, Color_green, true, -1); } } if (ms_Tunables.GetUseNewSlowDownCode() && (ms_Tunables.GetLockType() != LT_None) ) { if (m_pFreeAimAssistTarget) { weaponAssert(m_pPed->GetWeaponManager()); const CWeapon* pWeapon = m_pPed->GetWeaponManager()->GetEquippedWeapon(); if(pWeapon && pWeapon->GetWeaponInfo()->GetIsGunOrCanBeFiredLikeGun()) { CObject* pObject = m_pPed->GetWeaponManager()->GetEquippedWeaponObject(); if (pObject) { const Matrix34& mWeapon = RCC_MATRIX34(pObject->GetMatrixRef()); Vector3 vStart(Vector3::ZeroType); Vector3 vEnd(Vector3::ZeroType); if (pWeapon->CalcFireVecFromAimCamera(m_pPed->GetMyVehicle() != NULL ? static_cast(m_pPed->GetMyVehicle()) : m_pPed, mWeapon, vStart, vEnd)) { const camFrame& aimFrame = camInterface::GetPlayerControlCamAimFrame(); Vector3 vUp = aimFrame.GetUp(); Vector3 vSide = aimFrame.GetRight(); Vec3V vTargetPos = m_pFreeAimAssistTarget->GetTransform().GetPosition(); Vec3V vLengthOffset = VECTOR3_TO_VEC3V(vUp * ms_Tunables.GetReticuleSlowDownCapsuleLength() * 0.5f); Vec3V vCapsuleStart = vTargetPos - vLengthOffset; Vec3V vCapsuleEnd = vTargetPos + vLengthOffset; if (GetIsFreeAimAssistTargetPosWithinReticuleSlowDownConstraints(pWeapon, vStart, vEnd, false)) { grcDebugDraw::Line(RCC_VEC3V(vStart), RCC_VEC3V(vEnd), Color_green, -1); if (ms_Tunables.GetUseCapsuleTests()) grcDebugDraw::Capsule(vCapsuleStart, vCapsuleEnd, ms_Tunables.GetReticuleSlowDownCapsuleRadius(), Color_green, false, -1); else grcDebugDraw::Circle(m_pFreeAimAssistTarget->GetTransform().GetPosition(), ms_Tunables.GetReticuleSlowDownRadius(), Color_green, RCC_VEC3V(vSide), RCC_VEC3V(vUp), false, false, -1); } else { grcDebugDraw::Line(RCC_VEC3V(vStart), RCC_VEC3V(vEnd), Color_red, -1); if (ms_Tunables.GetUseCapsuleTests()) grcDebugDraw::Capsule(vCapsuleStart, vCapsuleEnd, ms_Tunables.GetReticuleSlowDownCapsuleRadius(), Color_red, false, -1); else grcDebugDraw::Circle(m_pFreeAimAssistTarget->GetTransform().GetPosition(), ms_Tunables.GetReticuleSlowDownRadius(), Color_red, RCC_VEC3V(vSide), RCC_VEC3V(vUp), false, false, -1); } } } } } } } if(ms_Tunables.m_DisplayAimAssistCurves) { // Draw some axes static Vector2 AXIS_ORIGIN(0.8f, 0.3f); static Vector2 AXIS_END(0.95f, 0.0733f); grcDebugDraw::Line(AXIS_ORIGIN, Vector2(AXIS_END.x, AXIS_ORIGIN.y), Color_red); grcDebugDraw::Line(AXIS_ORIGIN, Vector2(AXIS_ORIGIN.x, AXIS_END.y), Color_green); // Draw the camera input line grcDebugDraw::Line(Vector2(AXIS_ORIGIN.x + (AXIS_END.x - AXIS_ORIGIN.x) * m_fLastCameraInput, AXIS_ORIGIN.y), Vector2(AXIS_ORIGIN.x + (AXIS_END.x - AXIS_ORIGIN.x) * m_fLastCameraInput, AXIS_END.y), Color_HotPink); //! If we are strafing, need to clamp our scaled input so that it doesn't get to low as the reticule can "bounce" //! off our free aim assist target. Vector2 vMoveBlendRatio; m_pPed->GetMotionData()->GetCurrentMoveBlendRatio(vMoveBlendRatio); float fAimAssistDistanceResult = 1.0f; float fMinScaleInput = 0.0f; float fBlend = m_fCurveSetBlend; float fDistanceToFreeAimAssistTarget = 0.0f; if(abs(vMoveBlendRatio.x) > 0.0f) { vMoveBlendRatio.NormalizeSafe(); float fStrafeScale = abs(vMoveBlendRatio.x); if(m_pFreeAimAssistTarget) { Vector3 vLockOnPos = VEC3V_TO_VECTOR3(m_pFreeAimAssistTarget->GetTransform().GetPosition()); Vector3 vPedPos = VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition()); Vector3 vDistance = vLockOnPos - vPedPos; fDistanceToFreeAimAssistTarget = vDistance.XYMag(); fAimAssistDistanceResult = fStrafeScale * GetAimDistanceScaleCurve()->GetResult(fDistanceToFreeAimAssistTarget); if(ms_Tunables.m_UseReticuleSlowDownStrafeClamp) { fMinScaleInput = fAimAssistDistanceResult; } else { fBlend *= fAimAssistDistanceResult; } } } s32 iStart = 0, iEnd = CTargettingDifficultyInfo::CS_Max; bool bAccurateMode = IsCameraInAccurateMode(); float fCurrentResult = 0.0f; if(bAccurateMode) { iStart = CTargettingDifficultyInfo::CS_AimZoom; float fAimZoomValue = GetCurveSet(CTargettingDifficultyInfo::CS_AimZoom)->GetResult(m_fLastCameraInput); float fAimAssistZoomValue = GetCurveSet(CTargettingDifficultyInfo::CS_AimAssistZoom)->GetResult(m_fLastCameraInput); fCurrentResult = Lerp(fBlend, fAimZoomValue, fAimAssistZoomValue); } else { iEnd = CTargettingDifficultyInfo::CS_AimZoom; float fAimValue = GetCurveSet(CTargettingDifficultyInfo::CS_Aim)->GetResult(m_fLastCameraInput); float fAimAssistValue = GetCurveSet(CTargettingDifficultyInfo::CS_AimAssist)->GetResult(m_fLastCameraInput); fCurrentResult = Lerp(fBlend, fAimValue, fAimAssistValue); } if(ms_Tunables.m_UseReticuleSlowDownStrafeClamp && fCurrentResult < fMinScaleInput) { fCurrentResult = Min(fMinScaleInput, m_fLastCameraInput); } // Render our current result on the graph grcDebugDraw::Circle(Vector2(AXIS_ORIGIN.x + (AXIS_END.x - AXIS_ORIGIN.x) * m_fLastCameraInput, AXIS_ORIGIN.y + (AXIS_END.y - AXIS_ORIGIN.y) * fCurrentResult), 0.005f, Color_purple4); for(s32 i = iStart; i < iEnd; i++) { GetCurveSet(i)->Render(AXIS_ORIGIN, AXIS_END); } static Vector2 AXIS_ORIGIN_DISTANCE(0.8f, 0.6f); static Vector2 AXIS_END_DISTANCE(0.95f, 0.3733f); static dev_float AXIS_SCALE_DISTANCE = 250.0f; grcDebugDraw::Line(AXIS_ORIGIN_DISTANCE, Vector2(AXIS_END_DISTANCE.x, AXIS_ORIGIN_DISTANCE.y), Color_red); grcDebugDraw::Line(AXIS_ORIGIN_DISTANCE, Vector2(AXIS_ORIGIN_DISTANCE.x, AXIS_END_DISTANCE.y), Color_green); float fDistanceLine = Min(fDistanceToFreeAimAssistTarget/AXIS_SCALE_DISTANCE, 1.0f); // Draw the distance input line grcDebugDraw::Line(Vector2(AXIS_ORIGIN_DISTANCE.x + (AXIS_END_DISTANCE.x - AXIS_ORIGIN_DISTANCE.x) * fDistanceLine, AXIS_ORIGIN_DISTANCE.y), Vector2(AXIS_ORIGIN_DISTANCE.x + (AXIS_END_DISTANCE.x - AXIS_ORIGIN_DISTANCE.x) * fDistanceLine, AXIS_END_DISTANCE.y), Color_HotPink); GetAimDistanceScaleCurve()->Render(AXIS_ORIGIN_DISTANCE, AXIS_END_DISTANCE, AXIS_SCALE_DISTANCE); } } #endif // __BANK bool CPlayerPedTargeting::ShouldUpdateTargeting() const { //! If we haven't locked on to anything, then don't process whilst doing certain tasks. This allows us to process lock on immediately //! afterwards without using up our soft lock time. bool bDisableTargetingForTask = false; if(m_pLockOnTarget == NULL) { bDisableTargetingForTask = IsTaskDisablingTargeting(); } // Do not update if we don't have a weapon if (!GetWeaponInfo() || bDisableTargetingForTask) { return false; } return true; } bool CPlayerPedTargeting::IsTaskDisablingTargeting() const { if(m_pPed->GetPedResetFlag(CPED_RESET_FLAG_IsVaulting) || m_pPed->GetPedResetFlag(CPED_RESET_FLAG_IsJumping) || m_pPed->GetPedResetFlag(CPED_RESET_FLAG_IsFalling) || m_pPed->GetPedResetFlag(CPED_RESET_FLAG_IsLanding) || m_pPed->GetPedResetFlag(CPED_RESET_FLAG_IsExitingVehicle)) { return true; } return false; } bool CPlayerPedTargeting::CanIncrementSoftLockTimer() const { if(ms_Tunables.CanPauseSoftLockTimer()) { return !m_pPed->GetPedResetFlag(CPED_RESET_FLAG_IsReloading) && !IsTaskDisablingTargeting() FPS_MODE_SUPPORTED_ONLY( && (!m_pPed->IsFirstPersonShooterModeEnabledForPlayer(false) || !m_pPed->GetPedResetFlag(CPED_RESET_FLAG_DoingCombatRoll)) ); } return true; } bool CPlayerPedTargeting::ShouldUpdateFineAim(const CWeaponInfo& weaponInfo) { // Can't fine aim when using melee weapons if (weaponInfo.GetIsMelee()) { return false; } return true; } bool CPlayerPedTargeting::UpdateHardLockOn(const CWeapon* pWeapon, const CWeaponInfo& weaponInfo) { bool bClearFineAim = true; if (CannotLockOn(pWeapon, weaponInfo, HardLockOn) || GetShouldBreakLockOnWithTarget()) { return false; } if (m_pLockOnTarget) { if (m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON)) { bool bFindNewTarget = true; bool bNULLTarget = false; if (m_pLockOnTarget->GetIsTypePed()) { if(static_cast(m_pLockOnTarget.Get())->GetIsDeadOrDying()) { bFindNewTarget = false; if (m_fAssistedAimDeadPedLockOnTimer < 0.0f) { TUNE_GROUP_FLOAT(ASSISTED_AIM_TUNE, DEAD_PED_LOCKON_DURATION, g_DefaultDeadPedLockOnDuration, 0.0f, 5.0f, 0.1f); m_fAssistedAimDeadPedLockOnTimer = DEAD_PED_LOCKON_DURATION; } else { m_fAssistedAimDeadPedLockOnTimer -= fwTimer::GetTimeStep(); if (m_fAssistedAimDeadPedLockOnTimer <= 0.0f) { bFindNewTarget = true; bNULLTarget = true; } } } else { //! If we down our ped, don't count as downed for targeting check, otherwise we switch targets, which we don't want (unless they //! are ligitimately blocked etc). CPedTargetEvaluator::SetPedToIgnoreForDownedCheck(static_cast(m_pLockOnTarget.Get())); //! Allow searching for better targets after a min time. TUNE_GROUP_FLOAT(ASSISTED_AIM_TUNE, ASSISTED_AIM_MIN_LOCKON, 1.0f, 0.0f, 5.0f, 0.1f); if(m_fTimeLockedOn > ASSISTED_AIM_MIN_LOCKON) { bNULLTarget = true; } } } if (bFindNewTarget) { if(bNULLTarget) { m_pLockOnTarget = NULL; } FindAndSetLockOnTarget(); } CPedTargetEvaluator::SetPedToIgnoreForDownedCheck(NULL); } if (m_pLockOnTarget) { //! Don't allow target switching if below fine aim time. if(m_fTimeLockedOn < ms_Tunables.GetMinFineAimTime()) { m_bCheckForLeftTarget = false; m_bCheckForRightTarget = false; } UpdateLockOn(HardLockOn, m_pLockOnTarget); if (ShouldUpdateFineAim(weaponInfo)) { UpdateFineAim(); bClearFineAim = false; } } } else { m_fAssistedAimDeadPedLockOnTimer = -1.0f; // No lockon target currently, but want to only check in a certain direction relative to camera if (m_bCheckForLeftTarget || m_bCheckForRightTarget) { aiAssertf(!(m_bCheckForLeftTarget && m_bCheckForRightTarget), "Can't select a left and right target at the same time"); FindNextLockOnTarget(m_pLockOnTarget, m_bCheckForLeftTarget ? HD_LEFT : HD_RIGHT); } else { FindAndSetLockOnTarget(); } } if (bClearFineAim) { ClearFineAim(); } // Don't clear lock on return true; } bool CPlayerPedTargeting::UpdateSoftLockOn(const CWeapon* pWeapon, const CWeaponInfo& weaponInfo) { // NOTE: We don't allow an extended lock-on window in cover, as it can result in delayed lock-on when transitioning from idle to aiming/firing, // which has a negative impact on the perceived sync of the player and camera movement. float fSoftLockTimeToAcquireTarget = m_pPed->GetIsInCover() FPS_MODE_SUPPORTED_ONLY(&& !m_pPed->IsFirstPersonShooterModeEnabledForPlayer(false) )? ms_Tunables.GetSoftLockTimeToAcquireTargetInCover() : ms_Tunables.GetSoftLockTimeToAcquireTarget(); bool bCanLockOn = m_fTimeSoftLockedOn <= fSoftLockTimeToAcquireTarget || m_bCheckForLockOnSwitchExtension; bool bClearFineAim = true; //! If we kill target ped, trigger target switch extension time. if(m_pLockOnTarget && (m_pLockOnTarget != m_pLockOnSwitchExtensionTarget) && m_pLockOnTarget->GetIsTypePed() && ms_Tunables.GetLockOnSwitchTimeExtensionKillTarget() > 0) { CPed* pTargetPed = static_cast(m_pLockOnTarget.Get()); if(pTargetPed && pTargetPed->IsDead() && pTargetPed->GetSourceOfDeath()==m_pPed ) { m_uTimeLockOnSwitchExtensionExpires = Max(m_uTimeLockOnSwitchExtensionExpires, fwTimer::GetTimeInMilliseconds() + ms_Tunables.GetLockOnSwitchTimeExtensionKillTarget()); m_pLockOnSwitchExtensionTarget = m_pLockOnTarget; } } if (CannotLockOn(pWeapon, weaponInfo, SoftLockOn) || GetShouldBreakSoftLockTarget()) { m_bPlayerHasBrokenLockOn = true; return false; } if (m_pLockOnTarget) { // Allow switching targets? CEntity *pPreUpdateLockOnTarget = m_pLockOnTarget; //! Don't allow target switching until we have started fine aim. if(!m_bPlayerIsFineAiming) { m_bCheckForLeftTarget = false; m_bCheckForRightTarget = false; } UpdateLockOn(SoftLockOn, m_pLockOnTarget); // Reset lock on time if we switch targets? if(pPreUpdateLockOnTarget != m_pLockOnTarget) { ClearFineAim(); m_fTimeSoftLockedOn = 0.0f; m_bPlayerHasBrokenLockOn = false; m_bWasWithinFineAimLimits = false; } if (ms_Tunables.GetAllowSoftLockFineAim() && ShouldUpdateFineAim(weaponInfo)) { UpdateFineAim(ms_Tunables.GetUseFineAimSpring()); bClearFineAim = false; } } else if(m_pLockOnSwitchExtensionTarget && m_bCheckForLockOnSwitchExtension) { UpdateLockOn(SoftLockOn, m_pLockOnSwitchExtensionTarget); if(m_pLockOnTarget) { ClearFineAim(); m_fTimeSoftLockedOn = 0.0f; m_bPlayerHasBrokenLockOn = false; m_bWasWithinFineAimLimits = false; } m_bCheckForLockOnSwitchExtension = false; } else { if(bCanLockOn) { FindAndSetLockOnTarget(); } else if(!m_pLockOnTarget) { m_bPlayerHasBrokenLockOn = true; return false; } } if (bClearFineAim) { ClearFineAim(); } // Don't clear lock on return true; } void CPlayerPedTargeting::ProcessPedEvents() { if(m_pLockOnTarget) { ProcessPedEvents(m_pLockOnTarget); } if(m_pFreeAimTarget && (m_pFreeAimTarget != m_pLockOnTarget)) { ProcessPedEvents(m_pFreeAimTarget); } if(m_pFreeAimAssistTarget && (m_pFreeAimAssistTarget != m_pFreeAimTarget) && (m_pFreeAimAssistTarget != m_pLockOnTarget)) { ProcessPedEvents(m_pFreeAimAssistTarget); } } void CPlayerPedTargeting::ProcessPedEvents(CEntity* pEntity) { if(!pEntity || !pEntity->GetIsTypePed()) { return; } CPed* pTargetPed = static_cast(pEntity); #if LAZY_RAGDOLL_BOUNDS_UPDATE // Targeted Peds should have their bounds updated if (pTargetPed->GetRagdollInst()) { pTargetPed->RequestRagdollBoundsUpdate(); } #endif // If member ped is valid if( m_pPed ) { //Ensure the equipped weapon is valid. Assert( m_pPed->GetWeaponManager() ); const CWeaponInfo* pTurretWeaponInfo = m_pPed->GetWeaponManager()->GetEquippedTurretWeaponInfo(); const CWeapon* pEquippedWeapon = m_pPed->GetWeaponManager()->GetEquippedWeapon(); if(!pEquippedWeapon && !pTurretWeaponInfo) { return; } //Ensure the weapon info is valid. const CWeaponInfo* pWeaponInfo = pTurretWeaponInfo ? pTurretWeaponInfo : pEquippedWeapon->GetWeaponInfo(); if(!pWeaponInfo) { return; } // Don't fire any events if you are entering or exiting a vehicle. const bool bEnterTaskRunning = m_pPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_ENTER_VEHICLE, true); const bool bExitTaskRunning = m_pPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_EXIT_VEHICLE, true); if(bEnterTaskRunning || bExitTaskRunning) { return; } // Don't fire events when peeking from cover if( m_pPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_IN_COVER, true) && m_pPed->GetPedIntelligence()->GetQueriableInterface()->GetStateForTaskType(CTaskTypes::TASK_IN_COVER) == CTaskInCover::State_Peeking ) { return; } //Ensure the time aiming is valid. // Turret weapon infos are apparently counted as unarmed, so many of the checks below don't make sense for them. if (!pWeaponInfo->GetIsTurret()) { if(pWeaponInfo->GetIsUnarmed() || pWeaponInfo->GetIsMelee()) { static dev_float s_fMinTimeAiming = 0.6f; if(m_fTimeAiming < s_fMinTimeAiming) { return; } } else if(m_pPed->GetIsInVehicle()) { static dev_float s_fMinTimeAiming = 0.5f; if(m_fTimeAiming < s_fMinTimeAiming) { return; } } } static float s_fProjectileVisionRange = 2.0f; static float s_fDangerousMeleeWeaponVisionRange = 2.0f; // Compute distance to target squared float fDistSquared = VEC3V_TO_VECTOR3(pTargetPed->GetTransform().GetPosition()).Dist2(VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition())); // If within visual reaction range of the target float fRange = CEventShockingWeaponThreat::GetVisualReactionRange(); //Use the weapons range + a little if it's less than default. fRange = rage::Min(fRange, pWeaponInfo->GetRange() + 2.0f); if (pWeaponInfo->GetIsProjectile() && pWeaponInfo->GetIsThrownWeapon()) { // Projectiles have a much smaller visual reaction range than normal. fRange = s_fProjectileVisionRange; } // Don't add in weapon threat events for unarmed. if((!pWeaponInfo->GetIsUnarmed() || pWeaponInfo->GetIsTurret()) && !pWeaponInfo->GetIsNonViolent() && !pWeaponInfo->GetIsMelee() && !pTargetPed->PopTypeIsMission() && fDistSquared <= rage::square(fRange)) { // Emit a shocking event for the weapon threat, in case anyone witnesses it CEventShockingWeaponThreat shockEvent(*m_pPed, pTargetPed); CShockingEventsManager::Add(shockEvent); } // Check for gun aimed at event at target ped if (CEventGunAimedAt::IsAimingInTargetSeeingRange(*pTargetPed, *m_pPed, *pWeaponInfo)) { // Melee events have CEventMeleeAction as their targeting event. if ((pWeaponInfo->GetIsMelee() || pWeaponInfo->GetIsUnarmed()) && !pWeaponInfo->GetIsDangerousLookingMeleeWeapon() && !pWeaponInfo->GetIsTurret()) { const bool bIsOffensive = true; CEventMeleeAction eventMeleeAction(m_pPed, pTargetPed, bIsOffensive); pTargetPed->GetPedIntelligence()->AddEvent(eventMeleeAction); } // Otherwise use gun aimed at. else if (pWeaponInfo->GetIsNonViolent() && !pWeaponInfo->GetIsTurret()) { CPed* pFiringPed = static_cast(m_pPed); if (pFiringPed) { CEventShockingNonViolentWeaponAimedAt event(*pFiringPed); pTargetPed->GetPedIntelligence()->AddEvent(event); } } else if ((!pWeaponInfo->GetIsProjectile() || !pWeaponInfo->GetIsThrownWeapon() || fDistSquared <= square(s_fProjectileVisionRange)) && (!pWeaponInfo->GetIsDangerousLookingMeleeWeapon() || fDistSquared <= square(s_fDangerousMeleeWeaponVisionRange))) { CPed* pFiringPed = static_cast(m_pPed); CEventGunAimedAt event(pFiringPed); pTargetPed->GetPedIntelligence()->AddEvent(event); CEventFriendlyAimedAt eventFriendlyAimedAt(pFiringPed); pTargetPed->GetPedIntelligence()->AddEvent(eventFriendlyAimedAt); } } } } void CPlayerPedTargeting::ProcessPedEventsForFreeAimAssist() { if(m_FreeAimAssistProbeResults.GetResultsReady()) { for(WorldProbe::ResultIterator it = m_FreeAimAssistProbeResults.begin(); it < m_FreeAimAssistProbeResults.last_result(); ++it) { CEntity* pEntity = it->GetHitEntity(); if(pEntity && pEntity->GetIsTypePed() && (pEntity != m_pLockOnTarget) && (pEntity != m_pFreeAimTarget) && (pEntity != m_pFreeAimAssistTarget)) { ProcessPedEvents(pEntity); } else { // We are blocked by an object or map geometry -OR- We hit a non-entity. break; } } } } bool CPlayerPedTargeting::UpdateFreeAim(const CWeaponInfo& weaponInfo) { // Cache the weapon objects weaponAssert(m_pPed->GetWeaponManager()); const CWeapon* pWeapon = m_pPed->GetWeaponManager()->GetEquippedWeapon(); const CObject* pWeaponObject = m_pPed->GetWeaponManager()->GetEquippedWeaponObject(); if(pWeapon) { const Matrix34* mWeapon; if (pWeaponObject) mWeapon = &RCC_MATRIX34(pWeaponObject->GetMatrixRef()); else // Just use the peds matrix for taunts mWeapon = &RCC_MATRIX34(m_pPed->GetMatrixRef()); Vector3 vStart, vEnd; pWeapon->CalcFireVecFromAimCamera(m_pPed->GetMyVehicle() != NULL ? static_cast(m_pPed->GetMyVehicle()) : m_pPed, *mWeapon, vStart, vEnd); //! Override vEnd if it is a vehicle weapon. if(m_pPed->GetWeaponManager()->GetEquippedVehicleWeapon() && m_pPed->GetWeaponManager()->GetEquippedVehicleWeapon()->GetWeaponInfo()) { const camFrame& aimFrame = camInterface::GetPlayerControlCamAimFrame(); Vector3 vShot = aimFrame.GetFront(); vEnd = vStart + (m_pPed->GetWeaponManager()->GetEquippedVehicleWeapon()->GetWeaponInfo()->GetRange() * vShot); } // Search for the ped capsule under the reticule Vector3 vHitPos; FindFreeAimTarget(vStart, vEnd, weaponInfo, vHitPos); if(m_pFreeAimTargetRagdoll == NULL) { float fTestLength = m_pPed->GetIsInVehicle() ? ms_Tunables.GetAimAssistCapsuleMaxLengthInVehicle() : ms_Tunables.GetAimAssistCapsuleMaxLength(); if (GetAreCNCFreeAimImprovementsEnabled() && GetCurrentTargetingMode() == TargetingMode::FreeAim) { // Don't exceed the weapon range. const CWeaponInfo* pWeaponInfo = pWeapon->GetWeaponInfo(); const bool bAimingSniper = pWeaponInfo && pWeaponInfo->GetGroup() == WEAPONGROUP_SNIPER && camInterface::GetGameplayDirector().IsFirstPersonAiming(); float fMaxCapsuleLength = bAimingSniper ? ms_Tunables.GetAimAssistCapsuleMaxLengthFreeAimSniper() : ms_Tunables.GetAimAssistCapsuleMaxLengthFreeAim(); fTestLength = Min(fMaxCapsuleLength, weaponInfo.GetRange()); } // Search for the aim assist target // 5/10/13 - cthomas - Primarily for performance reasons, clamp the range of aim assist. Vector3 vClampedAimAssistFiringVec = vEnd - vStart; const float clampedAimAssistRange = Min(vClampedAimAssistFiringVec.Mag(), fTestLength); vClampedAimAssistFiringVec.Normalize(); vClampedAimAssistFiringVec.Scale(clampedAimAssistRange); Vector3 vEndPositionForFreeAimAssist = vStart + vClampedAimAssistFiringVec; FindFreeAimAssistTarget(vStart, vEndPositionForFreeAimAssist); } else { // Clear free aim assist target in the case of getting a valid ragdoll target m_pFreeAimAssistTarget = m_pFreeAimTargetRagdoll; m_vFreeAimAssistTargetPos = vHitPos; m_vClosestFreeAimAssistTargetPos = vHitPos; } } // Always clear lock on return false; } bool CPlayerPedTargeting::ProcessOnFootHomingLockOn(CPed* pPed) { if(pPed->IsLocalPlayer()) { weaponAssert(pPed->GetWeaponManager()); CWeapon* pEquippedWeapon = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeapon() : NULL; int iTargetingFlags = CPedTargetEvaluator::AIRCRAFT_WEAPON_TARGET_FLAGS|CPedTargetEvaluator::TS_HOMING_MISSLE_CHECK; iTargetingFlags &= ~CPedTargetEvaluator::TS_CHECK_PEDS; iTargetingFlags &= ~CPedTargetEvaluator::TS_IGNORE_VEHICLES_IN_LOS; // Find range of current weapon float fHeadingLimit = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetAngularLimitVehicleWeapon; float fPitchLimitMin = 0.0f; float fPitchLimitMax = 0.0f; float fWeaponRange = CVehicleWaterCannon::FIRETRUCK_CANNON_RANGE; float fRejectionWeaponRange = fWeaponRange; CEntity* pCurrentTarget = GetLockOnTarget(); const CWeaponInfo* pWeaponInfo = pEquippedWeapon ? pEquippedWeapon->GetWeaponInfo() : NULL; if( pWeaponInfo ) { const CAimingInfo* pAimingInfo = pWeaponInfo->GetAimingInfo(); if( pAimingInfo ) { fHeadingLimit = pAimingInfo->GetHeadingLimit(); fPitchLimitMin = pAimingInfo->GetSweepPitchMin(); fPitchLimitMax = pAimingInfo->GetSweepPitchMax(); } fWeaponRange = pWeaponInfo->GetRange(); fRejectionWeaponRange = fWeaponRange; } CPedTargetEvaluator::tHeadingLimitData headingLimitData(fHeadingLimit); static dev_float fExpandedHeadingLimitsTune = 30.0f; if( pCurrentTarget ) { bool bSelectingLeft = IsSelectingLeftOnFootHomingTarget(pPed); bool bSelectingRight = IsSelectingRightOnFootHomingTarget(pPed); if (bSelectingLeft || bSelectingRight) { iTargetingFlags |= bSelectingLeft ? CPedTargetEvaluator::TS_CYCLE_LEFT : iTargetingFlags |= CPedTargetEvaluator::TS_CYCLE_RIGHT; iTargetingFlags &= ~CPedTargetEvaluator::TS_PED_HEADING; // Ignore ped heading checks when cycling targets. iTargetingFlags &= ~CPedTargetEvaluator::TS_USE_DISTANCE_SCORING; // Ignore distance scoring to ensure we can cycle through all vehicles headingLimitData.fHeadingFalloffPower = 0.0f; // Use expanded heading limits when manually scanning for targets (target must be on screen). headingLimitData.bCamHeadingNeedsTargetOnScreen = true; headingLimitData.fHeadingAngularLimit = fExpandedHeadingLimitsTune; headingLimitData.fWideHeadingAngulerLimit = fExpandedHeadingLimitsTune; fHeadingLimit = fExpandedHeadingLimitsTune; } } bool bManualTargetSwitch = false; CEntity *pCurrentTargetPreSwitch = NULL; if(iTargetingFlags & (CPedTargetEvaluator::TS_CYCLE_LEFT | CPedTargetEvaluator::TS_CYCLE_RIGHT )) { bManualTargetSwitch = true; pCurrentTargetPreSwitch = pCurrentTarget; } CEntity* pFoundTarget = NULL; if(CPlayerInfo::IsAiming(false) && m_pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsAimingGun) && !pPed->GetIsInVehicle() && !m_pPed->GetPedResetFlag(CPED_RESET_FLAG_InAirDefenceSphere)) { pFoundTarget = CPedTargetEvaluator::FindTarget( pPed, iTargetingFlags, fWeaponRange, fRejectionWeaponRange, NULL, NULL, headingLimitData, fPitchLimitMin, fPitchLimitMax ); } //! If we tried to find a left/right target and failed, keep current target. if(pCurrentTargetPreSwitch && !pFoundTarget) { pFoundTarget = pCurrentTargetPreSwitch; } // Reset manual target timer if we've just tried to switch but landed on the same target. if (bManualTargetSwitch && pFoundTarget && pCurrentTarget && pFoundTarget == pCurrentTarget) { m_uManualSwitchTargetTime = fwTimer::GetTimeInMilliseconds(); } if(pFoundTarget) { // While we have a target, increment the potential target timer if( m_pPotentialTarget ) { m_fPotentialTargetTimer += fwTimer::GetTimeStep(); } // If we had no previous target, just set it if( !pCurrentTarget ) { ResetTimeAimingAtTarget(); SetLockOnTarget( pFoundTarget ); } // Does our potential target differ from our current? else if( pCurrentTarget != pFoundTarget ) { // Check to see the current target is valid and alive Vector3 vHeading = VEC3V_TO_VECTOR3( pPed->GetTransform().GetB() ); float fHeadingDifference = 0.0f; bool bOutsideMinHeading = false; bool bInInclusionRangeFailedHeading = false; int iIsValidFlags = iTargetingFlags; iIsValidFlags |= CPedTargetEvaluator::TS_CYCLE_LEFT; iIsValidFlags |= CPedTargetEvaluator::TS_CYCLE_RIGHT; bool bTargetInDeadzone = false; // Use expanded heading limits when cycling targets manually (ignore deadzone box checks). static dev_u32 s_uManualSwitchCooldown = 2500; float fExpandedHeadingLimit = headingLimitData.GetAngularLimit(); bool bManuallySwitchingTargets = bManualTargetSwitch || ((fwTimer::GetTimeInMilliseconds() - s_uManualSwitchCooldown) < m_uManualSwitchTargetTime); bool bDoDeadzoneBoxCheck = !bManuallySwitchingTargets; if (bManuallySwitchingTargets) { fExpandedHeadingLimit = fExpandedHeadingLimitsTune; // Check target validity using non-expanded heading limits. Don't allow lock on when outside of main box (by continually resetting aim timer). bool bCanLockOnToTarget = CPedTargetEvaluator::IsTargetValid( pPed->GetTransform().GetPosition(), pCurrentTarget->GetTransform().GetPosition(), iIsValidFlags, vHeading, fWeaponRange, fHeadingDifference, bOutsideMinHeading, bInInclusionRangeFailedHeading, bTargetInDeadzone, fHeadingLimit, fPitchLimitMin, fPitchLimitMax, true, false, false, bDoDeadzoneBoxCheck ); if (!bCanLockOnToTarget) { ResetTimeAimingAtTarget(); } } bool bIsValid = CPedTargetEvaluator::IsTargetValid( pPed->GetTransform().GetPosition(), pCurrentTarget->GetTransform().GetPosition(), iIsValidFlags, vHeading, fWeaponRange, fHeadingDifference, bOutsideMinHeading, bInInclusionRangeFailedHeading, bTargetInDeadzone, fExpandedHeadingLimit, fPitchLimitMin, fPitchLimitMax, true, false, false, bDoDeadzoneBoxCheck ); bool bIsAlive = pCurrentTarget->GetIsPhysical() && ((CPhysical*)pCurrentTarget)->GetHealth() > 0; // Homing specific logic if( bIsValid && bIsAlive && pWeaponInfo && pWeaponInfo->GetIsHoming()) { // Selected this manually. if( bManualTargetSwitch ) { ResetTimeAimingAtTarget(); SetLockOnTarget( pFoundTarget ); //! Selected target manually. Allow a longer cooldown between auto switching targets. m_uManualSwitchTargetTime = fwTimer::GetTimeInMilliseconds(); } // Do we have a new priority target to be? if( pFoundTarget != m_pPotentialTarget ) { // Cache off the new potential target and reset the change target timer m_pPotentialTarget = pFoundTarget; m_fPotentialTargetTimer = 0.0f; } // prevent switching targets every frame const CAmmoInfo* pAmmoInfo = pWeaponInfo->GetAmmoInfo(); if( pAmmoInfo && pAmmoInfo->GetClassId() == CAmmoRocketInfo::GetStaticClassId() ) { const CAmmoRocketInfo* pRocketInfo = static_cast(pAmmoInfo); Assert( pRocketInfo ); // Calculate the speed ratio based on the default speed float fMaxSpeed = rage::square( DEFAULT_MAX_SPEED ); float fSpeedRatio = rage::Min( pPed->GetVelocity().Mag2(), fMaxSpeed ) / fMaxSpeed; // Determine the time to switch targets based on the vehicle speed float fTimeToSwitchBetweenTargets = Lerp( fSpeedRatio, pRocketInfo->GetTimeBeforeSwitchTargetMax(), pRocketInfo->GetTimeBeforeSwitchTargetMin() ); // B*2171844: If our current target is outside of the box (ie in the deadzone) and we have a target within the actual box, swap immediately. if( (fTimeToSwitchBetweenTargets < m_fPotentialTargetTimer && ((m_uManualSwitchTargetTime == 0) || ((fwTimer::GetTimeInMilliseconds() - s_uManualSwitchCooldown) > m_uManualSwitchTargetTime))) || bTargetInDeadzone) { ResetTimeAimingAtTarget(); SetLockOnTarget( pFoundTarget ); m_uManualSwitchTargetTime = 0; return true; } } } // Otherwise just set the new target else { ResetTimeAimingAtTarget(); SetLockOnTarget( pFoundTarget ); return true; } } if(GetLockOnTarget() && GetLockOnTarget()->GetIsTypeVehicle()) { static_cast(GetLockOnTarget())->SetLastTimeHomedAt(fwTimer::GetTimeInMilliseconds()); } } // Clear the target if we don't have one else { m_pPotentialTarget = NULL; m_fPotentialTargetTimer = 0.0f; ResetTimeAimingAtTarget(); ClearLockOnTarget(); } #if __DEV // Debug draw the target for now static dev_bool bDebugDrawLockon = false; if(bDebugDrawLockon) { if(pCurrentTarget) { grcDebugDraw::BoxOriented(RCC_VEC3V(pCurrentTarget->GetBoundingBoxMin()),VECTOR3_TO_VEC3V(pCurrentTarget->GetBoundingBoxMax()), pCurrentTarget->GetMatrix(),Color_white); } } #endif } return false; } bool CPlayerPedTargeting::IsSelectingLeftOnFootHomingTarget(const CPed *pPed) { const CControl* pControl = pPed->GetControlFromPlayer(); if (pControl && (pControl->GetVehicleFlySelectLeftTarget().IsPressed())) { return true; } return false; } bool CPlayerPedTargeting::IsSelectingRightOnFootHomingTarget(const CPed *pPed) { const CControl* pControl = pPed->GetControlFromPlayer(); if (pControl && (pControl->GetVehicleFlySelectRightTarget().IsPressed())) { return true; } return false; } CPlayerPedTargeting::OnFootHomingLockOnState CPlayerPedTargeting::GetOnFootHomingLockOnState(const CPed *pPed) { if (pPed) { const CWeaponInfo *pWeaponInfo = pPed->GetWeaponManager() ? pPed->GetWeaponManager()->GetEquippedWeaponInfo() : NULL; // Time needed before the target is considered locked on. // Used to determine UI reticule style and whether to enable homing or not. float fTimeBeforeHoming = 0.0f; const CAmmoInfo* pAmmoInfo = pWeaponInfo ? pWeaponInfo->GetAmmoInfo() : NULL; if(pAmmoInfo && (pAmmoInfo->GetClassId() == CAmmoRocketInfo::GetStaticClassId()) ) { fTimeBeforeHoming = static_cast(pAmmoInfo)->GetTimeBeforeHoming(); } if (m_pLockOnTarget) { float fTotalLockOnTime = GetTimeAimingAtTarget(); if (fTotalLockOnTime < (fTimeBeforeHoming/2)) { // Display the green reticle. return OFH_NOT_LOCKED; } else if (fTotalLockOnTime < fTimeBeforeHoming) { // Display the flashing orange reticle. return OFH_ACQUIRING_LOCK_ON; } else { // Display the red reticle. // Enable homing. return OFH_LOCKED_ON; } } } return OFH_NO_TARGET; } void CPlayerPedTargeting::AddTargetableEntity(CEntity* pEntity) { const int iEntityIndex = m_TargetableEntities.Find((RegdEnt)pEntity); if (iEntityIndex == -1) { // Verify we haven't got any null entities in the array if it's full (hopefully shouldn't happen, just being safe!) if (m_TargetableEntities.IsFull()) { for (int i = 0; i < m_TargetableEntities.GetCount(); ++i) { CEntity* pEntity = m_TargetableEntities[i]; if (!pEntity) { m_TargetableEntities.DeleteFast(i); } } } if (pedVerifyf(m_TargetableEntities.GetCount() < m_TargetableEntities.GetMaxCount(), "CPlayerPedTargeting::s_iMaxTargetableEntities is full (MAX: %d)!", s_iMaxTargetableEntities)) { m_TargetableEntities.Append() = pEntity; } } } void CPlayerPedTargeting::RemoveTargetableEntity(CEntity* pEntity) { const int iIndex = m_TargetableEntities.Find((RegdEnt)pEntity); if (iIndex != -1) { m_TargetableEntities.DeleteFast(iIndex); } } void CPlayerPedTargeting::ClearTargetableEntities() { m_TargetableEntities.clear(); } CEntity* CPlayerPedTargeting::GetTargetableEntity(int iIndex) const { return m_TargetableEntities[iIndex].Get(); } bool CPlayerPedTargeting::GetIsEntityAlreadyRegisteredInTargetableArray(CEntity* pEntity) const { const int iIndex = m_TargetableEntities.Find((RegdEnt)pEntity); if (iIndex != -1) { return true; } return false; } #if __BANK void CPlayerPedTargeting::DebugAddTargetableEntity() { CEntity* pFocusEntity = CDebugScene::FocusEntities_Get(0); if (pFocusEntity) { CPed* pPlayerPed = CGameWorld::FindLocalPlayer(); if (pPlayerPed && pPlayerPed != pFocusEntity) { CPlayerInfo* pPlayerInfo = pPlayerPed->GetPlayerInfo(); if (pPlayerInfo) { CPlayerPedTargeting& rTargeting = pPlayerInfo->GetTargeting(); rTargeting.AddTargetableEntity(pFocusEntity); } } } } void CPlayerPedTargeting::DebugClearTargetableEntities() { CPed* pPlayerPed = CGameWorld::FindLocalPlayer(); if (pPlayerPed) { CPlayerInfo* pPlayerInfo = pPlayerPed->GetPlayerInfo(); if (pPlayerInfo) { CPlayerPedTargeting& rTargeting = pPlayerInfo->GetTargeting(); rTargeting.ClearTargetableEntities(); } } } #endif // __BANK void CPlayerPedTargeting::UpdateLockOn(const TargetingMode eTargetMode, CEntity *pCurrentLockOnEntity) { if (eTargetMode == HardLockOn || eTargetMode == SoftLockOn) { if (m_bCheckForLeftTarget) { FindNextLockOnTarget(pCurrentLockOnEntity, HD_LEFT); } else if (m_bCheckForRightTarget) { FindNextLockOnTarget(pCurrentLockOnEntity, HD_RIGHT); } #if __BANK if(CPedTargetEvaluator::DEBUG_TARGET_SWITCHING_SCORING_ENABLED) { #if DEBUG_DRAW CTask::ms_debugDraw.ClearAll(); #endif CPedTargetEvaluator::EnableDebugScoring(true); switch(CPedTargetEvaluator::DEBUG_TARGETSCORING_DIRECTION) { case(CPedTargetEvaluator::eDSD_Left): FindNextLockOnTarget(pCurrentLockOnEntity, HD_LEFT, false); break; case(CPedTargetEvaluator::eDSD_Right): FindNextLockOnTarget(pCurrentLockOnEntity, HD_RIGHT, false); break; default: break; } CPedTargetEvaluator::EnableDebugScoring(false); } #endif m_fTimeLockedOn += fwTimer::GetTimeStep_NonScaledClipped(); } } bool CPlayerPedTargeting::CanPedLockOnWithWeapon(const CWeapon* pWeapon, const CWeaponInfo& weaponInfo) { bool bCanLockOn = false; if(m_pPed->GetIsDrivingVehicle()) { if(weaponInfo.GetForcingDriveByAssistedAim()) { bCanLockOn = true; } else if(ms_Tunables.GetUseDriveByAssistedAim()) { CVehicle *pVehicle = m_pPed->GetVehiclePedInside(); if(pVehicle->GetVelocity().Mag2() >= (ms_Tunables.GetMinVelocityForDriveByAssistedAim() * ms_Tunables.GetMinVelocityForDriveByAssistedAim())) { bCanLockOn = true; } } } else if(m_pPed->GetIsInVehicle()) { if(!m_pPed->IsInFirstPersonVehicleCamera()) { bCanLockOn = pWeapon ? pWeapon->GetCanLockonInVehicle() : weaponInfo.GetCanLockOnInVehicle(); } } else { bCanLockOn = pWeapon ? pWeapon->GetCanLockonOnFoot() : weaponInfo.GetCanLockOnOnFoot(); } //! Disable lock on inside planes as camera is too far out for target selection to work well. if(m_pPed->GetVehiclePedInside() && m_pPed->GetVehiclePedInside()->InheritsFromPlane()) { bCanLockOn = false; } return bCanLockOn; } bool CPlayerPedTargeting::CannotLockOn(const CWeapon* pWeapon, const CWeaponInfo& weaponInfo, const TargetingMode eTargetMode) { pedFatalAssertf(m_pPed, "m_pPed is NULL"); bool bCanLockOn = CanPedLockOnWithWeapon(pWeapon, weaponInfo); if (!bCanLockOn) { return true; } //! Allow lock on for passengers in MP now. /*if (NetworkInterface::IsGameInProgress() && bIsInVehicle && !m_pPed->GetIsDrivingVehicle()) { return true; }*/ if (m_pPed->GetPedResetFlag(CPED_RESET_FLAG_ClearLockonTarget)) { return true; } //! Remove lock if target becomes invisible. if(m_pLockOnTarget && !m_pLockOnTarget->GetIsVisible()) { return true; } // Do not allow lock on for peds in driver seat unless we're assisted aiming if (!CTaskAimGunVehicleDriveBy::ms_UseAssistedAiming && !m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON) && m_pPed->GetMyVehicle() && !CNetwork::IsGameInProgress()) { const bool bVehicleHasDriver = m_pPed->GetMyVehicle()->GetLayoutInfo()->GetHasDriver(); if (bVehicleHasDriver && m_pPed->GetMyVehicle()->GetSeatManager()->GetDriver() == m_pPed) { return true; } } // Break lock on if target is dead and player is not really pushing the aim stick TUNE_GROUP_FLOAT(PLAYER_TARGETING, DEAD_PED_LOCK_ON_BREAK_VALUE, 50.0f, 0.0f, 100.0f, 1.0f); if (eTargetMode == SoftLockOn) { const float fTimeStep = fwTimer::GetTimeStep(); if(CanIncrementSoftLockTimer()) { m_fTimeSoftLockedOn += fTimeStep; } if(!m_bCheckForLockOnSwitchExtension) { if ((m_fTimeSoftLockedOn > ms_Tunables.GetSoftLockTime()) TARGET_SEQUENCE_ONLY(&& !IsUsingTargetSequence())) { return true; } else { // If allowing fine aim during soft lock, only consider soft lock as broken if we're outside the fine aim limits bool bWantsToBreak = false; float fBreakTime = m_fFineAimHorizontalSpeedWeight >= 1.0f ? ms_Tunables.GetMinSoftLockBreakAtMaxXStickTime() : ms_Tunables.GetMinSoftLockBreakTime(); if(ms_Tunables.GetAllowSoftLockFineAim()) { const bool bWithinFineAimLimits = IsFineAimWithinBreakLimits(); if (m_bWasWithinFineAimLimits) { bWantsToBreak = !bWithinFineAimLimits; } else { m_bWasWithinFineAimLimits = bWithinFineAimLimits; } if(m_pLockOnTarget) { Vector3 vLockOnPos = VEC3V_TO_VECTOR3(m_pLockOnTarget->GetTransform().GetPosition()); Vector3 vPedPos = VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition()); Vector3 vDistance = vLockOnPos - vPedPos; float fDist = Clamp(vDistance.Mag(), ms_Tunables.GetSoftLockBreakDistanceMin(), ms_Tunables.GetSoftLockBreakDistanceMax()); float fWeight = (fDist - ms_Tunables.GetSoftLockBreakDistanceMin()) / (ms_Tunables.GetSoftLockBreakDistanceMax() - ms_Tunables.GetSoftLockBreakDistanceMin()); fWeight = Clamp(fWeight, 0.0f, 1.0f); float fAbsoluteLimits = ms_Tunables.GetSoftLockFineAimXYAbsoluteValueClose() + ((ms_Tunables.GetSoftLockFineAimXYAbsoluteValue() - ms_Tunables.GetSoftLockFineAimXYAbsoluteValueClose()) * fWeight); //! Break immediately if we get outside max XY limits and we are pushing stick to break. if(!IsFineAimWithinAbsoluteXYLimits(fAbsoluteLimits) && m_fFineAimHorizontalSpeedWeight > 0.25f) { fBreakTime = 0.0f; } else if(!IsFineAimWithinBreakXYLimits() && m_pLockOnTarget) { //! If outside XY limits, reduce break out time if close to target to stop camera from spinning. Note: We ignore Z, as this //! is ok. fBreakTime = ms_Tunables.GetMinSoftLockBreakTimeCloseRange() + ((ms_Tunables.GetMinSoftLockBreakTime() - ms_Tunables.GetMinSoftLockBreakTimeCloseRange()) * fWeight); } } } else { bWantsToBreak = IsPushingStick(ms_Tunables.GetSoftLockBreakValue()); } if (bWantsToBreak) { m_fTimeTryingToBreakSoftLock += fTimeStep; if(m_fTimeTryingToBreakSoftLock >= fBreakTime) { if(ms_Tunables.GetLockOnSwitchTimeExtensionBreakLock() > 0) { m_pLockOnSwitchExtensionTarget = m_pLockOnTarget; m_uTimeLockOnSwitchExtensionExpires = Max(m_uTimeLockOnSwitchExtensionExpires, fwTimer::GetTimeInMilliseconds() + ms_Tunables.GetLockOnSwitchTimeExtensionBreakLock()); } return true; }; } else { m_fTimeTryingToBreakSoftLock = 0.0f; } } } } // If we have a dead lockon target, disable lockon if not pushing the aim stick // if (HasDeadLockonTarget()) // { // return !IsPushingStick(DEAD_PED_LOCK_ON_BREAK_VALUE); // } return false; } #if USE_TARGET_SEQUENCES bool CPlayerPedTargeting::IsTargetSequenceReadyToFire() const { if (m_pLockOnTarget) { return m_TargetSequenceHelper.IsReadyToFire(); } else { return true; } } bool CPlayerPedTargeting::IsUsingTargetSequence() const { return m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON) && m_pLockOnTarget && m_TargetSequenceHelper.HasSequence(); } #endif // USE_TARGET_SEQUENCES bool CPlayerPedTargeting::IsPrecisionAiming()const { Vector3 vUnused; return GetFineAimingOffset(vUnused) TARGET_SEQUENCE_ONLY(|| IsUsingTargetSequence() || m_TargetSequenceHelper.IsSequenceFinished()); } void CPlayerPedTargeting::FinaliseCurveSets() { for(s32 i = 0; i < CTargettingDifficultyInfo::CS_Max; i++) { ms_Tunables.m_FirstPersonEasyTargettingDifficultyInfo.m_CurveSets[i].Finalise(); ms_Tunables.m_EasyTargettingDifficultyInfo.m_CurveSets[i].Finalise(); ms_Tunables.m_FirstPersonNormalTargettingDifficultyInfo.m_CurveSets[i].Finalise(); ms_Tunables.m_NormalTargettingDifficultyInfo.m_CurveSets[i].Finalise(); ms_Tunables.m_FirstPersonHardTargettingDifficultyInfo.m_CurveSets[i].Finalise(); ms_Tunables.m_HardTargettingDifficultyInfo.m_CurveSets[i].Finalise(); ms_Tunables.m_FirstPersonExpertTargettingDifficultyInfo.m_CurveSets[i].Finalise(); ms_Tunables.m_ExpertTargettingDifficultyInfo.m_CurveSets[i].Finalise(); } } bool CPlayerPedTargeting::IsDisabledDueToWeaponWheel() const { bool bDisableDueToWeaponWheel = CNewHud::IsShowingHUDMenu() && !m_pLockOnTarget; return bDisableDueToWeaponWheel; } bool CPlayerPedTargeting::IsLockOnDisabled() const { if (m_pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_DisablePlayerLockon) || IsDisabledDueToWeaponWheel() || (m_pPed->GetPedIntelligence() && m_pPed->GetPedIntelligence()->GetQueriableInterface()->IsTaskCurrentlyRunning(CTaskTypes::TASK_CLIMB_LADDER))) { return true; } return false; } CPlayerPedTargeting::TargetingMode CPlayerPedTargeting::GetTargetingMode(const CWeapon* pWeapon, const CWeaponInfo& weaponInfo) { pedFatalAssertf(m_pPed, "m_pPed is NULL"); eLockType eTargetLockType = ms_Tunables.GetLockType(); if (!IsLockOnDisabled()) { bool bSoftFiringNotAiming = fwTimer::GetTimeInMilliseconds() < (m_uTimeSinceSoftFiringAndNotAiming + 100); bool bCanLockOn = CanPedLockOnWithWeapon(pWeapon, weaponInfo); // B*6465133: Free aim assisted (ie snap lock) shouldn't allow lock-on if already aiming over a target ped (ie we have a free aim target ped). if (CPedTargetEvaluator::GetTargetMode(m_pPed) == CPedTargetEvaluator::TARGETING_OPTION_ASSISTED_FREEAIM) { if (!m_pLockOnTarget && m_pFreeAimTarget && m_pFreeAimTarget->GetIsTypePed()) { bCanLockOn = false; } } if( CPlayerInfo::IsFlyingAircraft() || weaponInfo.GetUseManualTargetingMode() ) { return Manual; } else if( (CPlayerInfo::IsHardAiming() || CPlayerInfo::IsDriverFiring() || m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON)) && bCanLockOn ) { if(weaponInfo.GetForcingDriveByAssistedAim()) //force hard lock on for cars that force assisted aim? { return HardLockOn; } else if( m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON) && !m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_NO_RETICULE_AIM_ASSIST_ON)) { if (!m_pPed->GetIsDrivingVehicle() || eTargetLockType != LT_None) { return HardLockOn; } } if( eTargetLockType == LT_Hard || (weaponInfo.GetIsMelee() && !IsMeleeTargetingUsingNormalLock()) ) { bool bSelectingLeftTarget = false; bool bSelectingRightTarget = false; if(GetWeaponInfo()) { bSelectingLeftTarget = CPlayerInfo::IsSelectingLeftTarget(); bSelectingRightTarget = CPlayerInfo::IsSelectingRightTarget(); } //! Don't oscillate between free aim and hard lock during melee. bool bForceMeleeHardLock = (weaponInfo.GetIsMelee() && !IsMeleeTargetingUsingNormalLock()); if ( bForceMeleeHardLock || !m_bPlayerHasStartedToAim || bSoftFiringNotAiming || m_bPlayerHasBrokenLockOn || m_pLockOnTarget || bSelectingLeftTarget || bSelectingRightTarget) { if (!m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON)) { m_bCheckForLeftTarget = bSelectingLeftTarget; m_bCheckForRightTarget = bSelectingRightTarget; m_bPlayerHasStartedToAim = true; m_bPlayerHasBrokenLockOn = false; } return HardLockOn; } } else if ( eTargetLockType == LT_Soft ) { if (CPlayerPedTargeting::ms_Tunables.GetUseLockOnTargetSwitching()) { if(CPlayerInfo::IsSelectingLeftTarget()) { m_bCheckForLeftTarget = true; } if(CPlayerInfo::IsSelectingRightTarget()) { m_bCheckForRightTarget = true; } } m_bCheckForLockOnSwitchExtension = false; if (!m_bPlayerHasStartedToAim || bSoftFiringNotAiming) { m_bPlayerHasStartedToAim = true; m_bPlayerHasBrokenLockOn = false; return SoftLockOn; } else // Player has started to soft aim { // If player hasn't broken lockon and has been locked on less than the max lock on time then continue soft lock if (!m_bPlayerHasBrokenLockOn && ((m_fTimeSoftLockedOn < ms_Tunables.GetSoftLockTime()) TARGET_SEQUENCE_ONLY(|| (IsUsingTargetSequence())))) { return SoftLockOn; } u32 nCurrentTime = fwTimer::GetTimeInMilliseconds(); if( (m_bCheckForLeftTarget || m_bCheckForRightTarget) && m_pLockOnSwitchExtensionTarget && (nCurrentTime < m_uTimeLockOnSwitchExtensionExpires) ) { m_bCheckForLockOnSwitchExtension = true; return SoftLockOn; } m_bPlayerHasBrokenLockOn = true; } // If player has broken a lock on or didn't have a target when beginning to aim // fall through to free aim if possible } } } bool bPlayerIsSoftFiringNotAiming = CPlayerInfo::IsSoftFiring() && !CPlayerInfo::IsAiming(false) && !CPlayerInfo::IsDriverFiring(); if(bPlayerIsSoftFiringNotAiming) { m_uTimeSinceSoftFiringAndNotAiming = fwTimer::GetTimeInMilliseconds(); } // Reset soft aim variables if not aiming so we can reacquire a lock on target if( eTargetLockType == LT_Soft ) { if ( (bPlayerIsSoftFiringNotAiming || !CPlayerInfo::IsAiming()) && !CPlayerInfo::IsDriverFiring()) { ClearSoftAim(); } } if (weaponInfo.GetCanFreeAim() && (CPlayerInfo::IsAiming() || CPlayerInfo::IsDriverFiring()) && !IsLockOnDisabled()) { // If we've been free aiming for a bit, do not allow player to then lock on static bool ENABLE_FREE_AIM_CHECK = true; if( eTargetLockType == LT_Soft && ENABLE_FREE_AIM_CHECK ) { dev_float MAX_TIME_TO_ALLOW_INITIAL_SOFT_LOCK = 0.25f; if (m_fTimeAiming > MAX_TIME_TO_ALLOW_INITIAL_SOFT_LOCK) { m_bPlayerHasStartedToAim = true; m_bPlayerHasBrokenLockOn = true; } } else if( eTargetLockType == LT_Hard && !CPlayerInfo::IsHardAiming() ) { m_bPlayerHasBrokenLockOn = true; } m_pPed->GetPlayerInfo()->SetPlayerDataFreeAiming(true); return FreeAim; } m_bPlayerHasStartedToAim = false; m_uTimeSinceSoftFiringAndNotAiming = 0; m_pPed->GetPlayerInfo()->SetPlayerDataFreeAiming(false); return None; } void CPlayerPedTargeting::UpdateLockOnTarget() { pedFatalAssertf(m_pPed, "m_pPed is NULL"); const CWeaponInfo* pWeaponInfo = GetWeaponInfo(); if(!pWeaponInfo) { // No weapon return; } CWeapon* pWeapon = m_pPed->GetWeaponManager()->GetEquippedWeapon(); bool bCanLockon = CanPedLockOnWithWeapon(pWeapon, *pWeaponInfo); if(!bCanLockon) { // Selected weapon can not lock-on, clear target ClearLockOnTarget(); } bool bAssistedAiming = m_pPed->GetPlayerInfo()->GetPlayerResetFlags().IsFlagSet(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON); // Do not allow lock on for peds in driver seat if (m_pPed->GetMyVehicle()) { const bool bVehicleHasDriver = m_pPed->GetMyVehicle()->GetLayoutInfo()->GetHasDriver(); if (!bVehicleHasDriver || m_pPed->GetMyVehicle()->GetSeatManager()->GetDriver() == m_pPed) { ClearLockOnTarget(); return; } } if((bCanLockon && (bAssistedAiming || !m_pPed->GetPlayerInfo()->GetPlayerDataFreeAiming() || CPlayerInfo::IsJustHardAiming()))) { // If not locked-on, find a target if(!m_pLockOnTarget) { if(bAssistedAiming || CPlayerInfo::IsJustHardAiming() || (CPlayerInfo::IsHardAiming() && !CTaskPlayerOnFoot::ms_bAnalogueLockonAimControl)) { FindAndSetLockOnTarget(); if (bAssistedAiming && m_pLockOnTarget) { Vector3 vecAim = camInterface::GetPlayerControlCamAimFrame().GetFront(); vecAim.z = 0.0f; vecAim.Normalize(); Vector3 vToTarget = VEC3V_TO_VECTOR3(m_pLockOnTarget->GetTransform().GetPosition() - m_pPed->GetTransform().GetPosition()); vToTarget.z = 0.0f; vToTarget.Normalize(); const float fDot = vecAim.Dot(vToTarget); TUNE_GROUP_FLOAT(ONE_H_AIM, dotTolerance2, 0.85f, 0.0f, 1.0f, 0.01f); if (fDot <= dotTolerance2) { ClearLockOnTarget(); } if(ShouldUsePullGunSpeech(pWeaponInfo)) m_pPed->NewSay("PULL_GUN"); } } else { ClearLockOnTarget(); } } // Already locked on, update lock on camera mode else { if(GetShouldBreakLockOnWithTarget()) { ClearLockOnTarget(); } // If we already have a target we can find neighbouring targets if the player presses the L R buttons else if(m_pLockOnTarget) { if(CPlayerInfo::IsSelectingLeftTarget()) { FindNextLockOnTarget(m_pLockOnTarget, HD_LEFT); } else if(CPlayerInfo::IsSelectingRightTarget()) { FindNextLockOnTarget(m_pLockOnTarget, HD_RIGHT); } if(!m_pPed->GetCoverPoint()) { m_pPed->SetDesiredHeading(VEC3V_TO_VECTOR3(m_pLockOnTarget->GetTransform().GetPosition())); } CControl* pControl = m_pPed->GetControlFromPlayer(); Vector2 vecStickInput; vecStickInput.x = pControl->GetPedWalkLeftRight().GetNorm(); vecStickInput.y = pControl->GetPedWalkUpDown().GetNorm(); static dev_float s_fStickThreshold = 0.0f; // Prevent ped from continually moving forward when locked on and making small movements if no input is received if (Abs(vecStickInput.x) > s_fStickThreshold || Abs(vecStickInput.y) > s_fStickThreshold) { m_pPed->SetIsStrafing(true); } else { m_pPed->SetIsStrafing(false); } } } } else if(m_pPed->GetPlayerInfo()->GetPlayerDataFreeAiming() && bCanLockon && (m_pPed->GetPlayerInfo()->IsHardAiming() || m_pPed->GetPlayerInfo()->IsFiring() || m_pPed->GetPlayerInfo()->IsSelectingLeftTarget() || m_pPed->GetPlayerInfo()->IsSelectingRightTarget())) { if(m_pPed->GetPlayerInfo()->IsSelectingLeftTarget()) { FindNextLockOnTarget(m_pLockOnTarget, HD_LEFT); } else if(m_pPed->GetPlayerInfo()->IsSelectingRightTarget()) { FindNextLockOnTarget(m_pLockOnTarget, HD_RIGHT); } } if(!bAssistedAiming && !m_pLockOnTarget && pWeaponInfo->GetCanFreeAim() && (m_pPed->GetPlayerInfo()->IsAiming() || m_pPed->GetPlayerInfo()->IsFiring())) { m_pPed->GetPlayerInfo()->SetPlayerDataFreeAiming( true ); } else { m_pPed->GetPlayerInfo()->SetPlayerDataFreeAiming( false ); } m_fLockOnRangeOverride = -1.0f; // Clear the weapon range override - needs to be set every frame from script } void CPlayerPedTargeting::UpdateLockOnTargetPos() { pedFatalAssertf(m_pPed, "m_pPed is NULL"); const bool bIsAssistedAiming = m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON); Vector3 vCurrentAimAtPos(Vector3::ZeroType); const CWeapon* pWeapon = m_pPed->GetWeaponManager() ? m_pPed->GetWeaponManager()->GetEquippedWeapon() : NULL; const CObject* pWeaponObject = m_pPed->GetWeaponManager() ? m_pPed->GetWeaponManager()->GetEquippedWeaponObject() : NULL; Vector3 vStart(Vector3::ZeroType); if (pWeapon && pWeaponObject) { const Matrix34& mWeapon = RCC_MATRIX34(pWeaponObject->GetMatrixRef()); pWeapon->CalcFireVecFromAimCamera(m_pPed, mWeapon, vStart, vCurrentAimAtPos); } // Update the sequence helper TARGET_SEQUENCE_ONLY(m_TargetSequenceHelper.UpdateTime();) if (m_pLockOnTarget || bIsAssistedAiming) { static dev_float NORMAL_LERP_SPEED = 0.6f; TUNE_GROUP_FLOAT(PLAYER_TARGETING, ASSISTED_AIMING_APPROACH_RATE, 5.0f, 0.0f, 50.0f, 0.1f); TUNE_GROUP_FLOAT(PLAYER_TARGETING, ASSISTED_AIMING_TARGET_SEQUENCE_APPROACH_RATE, 50.0f, 0.0f, 1000.0f, 0.1f); if (m_pLockOnTarget) { #if USE_TARGET_SEQUENCES if (IsUsingTargetSequence() && bIsAssistedAiming) { if(m_pLockOnTarget->GetIsTypePed()) { CPed* pTargetPed = static_cast(m_pLockOnTarget.Get()); if (pTargetPed->GetIsDeadOrDying()) { m_TargetSequenceHelper.Reset(); } else { pedFatalAssertf(m_pPed->GetPlayerInfo(), "m_pPed GetPlayerInfo() is NULL"); // If the player is pressing fire repeatedly, override the timing in the target sequence if (m_pPed->GetPlayerInfo()->IsJustFiring()) { m_TargetSequenceHelper.ForceShotReady(); } m_TargetSequenceHelper.UpdateTargetPosition(static_cast(m_pLockOnTarget.Get()), vCurrentAimAtPos); } } else { m_pLockOnTarget->GetLockOnTargetAimAtPos(vCurrentAimAtPos); } if (m_pPed->GetIsDrivingVehicle() || !m_bHasUpdatedLockOnPos) { m_vLockOnTargetPos = vCurrentAimAtPos; m_bHasUpdatedLockOnPos = true; } } else #endif // USE_TARGET_SEQUENCES { if (m_pPed->GetIsDrivingVehicle() || (bIsAssistedAiming || !m_bHasUpdatedLockOnPos)) { // We haven't updated the initial lock on position so it will currently be zero, just set it // to be the target position m_pLockOnTarget->GetLockOnTargetAimAtPos(m_vLockOnTargetPos); m_bHasUpdatedLockOnPos = true; } m_pLockOnTarget->GetLockOnTargetAimAtPos(vCurrentAimAtPos); } } if (bIsAssistedAiming) { //! DMKH. Removing this. It doesn't make sense to alter the target position here. If this causes a problem for external code, they //! should fix it themselves. /*if (!IsUsingTargetSequence()) { Vector3 vToAimPos = vCurrentAimAtPos - vStart; vToAimPos.Normalize(); vCurrentAimAtPos = vStart + vToAimPos * CTaskAimGunOnFoot::ms_fTargetDistanceFromCameraForAimIk; #if DEBUG_DRAW CPedAccuracy::ms_debugDraw.AddSphere(RCC_VEC3V(vCurrentAimAtPos), 0.025f, Color_purple, 100); #endif }*/ if (!m_pLockOnTarget) { m_vLockOnTargetPos = vCurrentAimAtPos; } } TUNE_GROUP_BOOL(PLAYER_TARGETING, USE_LERP, false); if (bIsAssistedAiming && !USE_LERP) { m_vLockOnTargetPos.ApproachStraight(vCurrentAimAtPos, TARGET_SEQUENCE_ONLY(IsUsingTargetSequence() ? ASSISTED_AIMING_TARGET_SEQUENCE_APPROACH_RATE :) ASSISTED_AIMING_APPROACH_RATE, fwTimer::GetTimeStep()); } else { TUNE_GROUP_FLOAT(PLAYER_TARGETING, ASSISTED_AIMING_LERP_RATE, 0.1f, 0.0f, 1.0f, 0.01f); float fLerpSpeed = bIsAssistedAiming ? ASSISTED_AIMING_LERP_RATE :NORMAL_LERP_SPEED; fLerpSpeed *= fwTimer::GetTimeStep() / (1.0f / 30.0f); m_vLockOnTargetPos.Lerp(fLerpSpeed, vCurrentAimAtPos); } Vector3 vDiff = m_vLockOnTargetPos - vCurrentAimAtPos; static dev_float MAX_OFFSET = 0.75f; if(!bIsAssistedAiming && vDiff.Mag2() > rage::square(MAX_OFFSET)) { vDiff.Normalize(); vDiff.Scale(MAX_OFFSET); m_vLockOnTargetPos = vCurrentAimAtPos + vDiff; } #if DEBUG_DRAW static u32 LOCK_ON_TARGET_POS_DEBUG_1 = ATSTRINGHASH("LOCK_ON_TARGET_POS_DEBUG_1", 0x50CE1003); static u32 LOCK_ON_TARGET_POS_DEBUG_2 = ATSTRINGHASH("LOCK_ON_TARGET_POS_DEBUG_2", 0x48BFFFE7); CPedAccuracy::ms_debugDraw.AddLine(RCC_VEC3V(vCurrentAimAtPos), RCC_VEC3V(m_vLockOnTargetPos), Color_red, 100, LOCK_ON_TARGET_POS_DEBUG_1); CPedAccuracy::ms_debugDraw.AddSphere(RCC_VEC3V(m_vLockOnTargetPos), 0.025f, Color_red, 100, LOCK_ON_TARGET_POS_DEBUG_2); #endif // DEBUG_DRAW } else if (!bIsAssistedAiming) { // Clear the position m_vLockOnTargetPos.Zero(); } if(NetworkInterface::IsGameInProgress() && m_pLockOnTarget) { //! Test warp /*static dev_bool s_bTestWarp = false; if(m_fTimeAiming > 1.0f && s_bTestWarp) { Vector3 vTestWarpPos = VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition()); static dev_float s_fTestWarpA = 20.0f; static dev_float s_fTestWarpB = -20.0f; vTestWarpPos += VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetA()) * s_fTestWarpA; vTestWarpPos += VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetB()) * s_fTestWarpB; m_pLockOnTarget->SetPosition(vTestWarpPos); }*/ //! Do a test to see if target has warped. static dev_float s_fWarpTolerance = 10.0f; if(!m_vLockOnTargetPos.IsClose(m_vLastLockOnTargetPos, s_fWarpTolerance) ) { ClearLockOnTarget(); } } m_vLastLockOnTargetPos = m_vLockOnTargetPos; } s32 CPlayerPedTargeting::GetFindTargetFlags() const { // Construct targeting flags s32 iFlags = CPedTargetEvaluator::DEFAULT_TARGET_FLAGS; // Allow auto targeting of players in vehicles if we're on foot if (NetworkInterface::IsGameInProgress() && !m_pPed->GetIsInVehicle()) { iFlags |= CPedTargetEvaluator::TS_LOCKON_TO_PLAYERS_IN_VEHICLES; } // If we have been aiming for a while, disable the distance scoring // This will allow us to more accurately lock-on to peds under the reticule static dev_float AIM_TIME_TO_IGNORE_DISTANCE = 0.8f; if(m_fTimeAiming > AIM_TIME_TO_IGNORE_DISTANCE) { iFlags &= ~CPedTargetEvaluator::TS_USE_DISTANCE_SCORING; } // If the weapon can lock-on to vehicles, or we are in a vehicle, enable all vehicles as targets const CWeaponInfo* pWeaponInfo = GetWeaponInfo(); pedFatalAssertf(pWeaponInfo, "Weapon Info is NULL"); if(pWeaponInfo->GetIsHoming() || m_pPed->GetVehiclePedInside() != NULL) { iFlags |= CPedTargetEvaluator::TS_CHECK_VEHICLES; } //! Do Aim vector bonus. static dev_bool s_bDoAimBonus = false; if(s_bDoAimBonus) { iFlags |= CPedTargetEvaluator::TS_DO_AIMVECTOR_BONUS_AND_TASK_MODIFIER; } bool bUseCoverVantagePoint = false; #if __BANK if(PARAM_enablecovervantagepointforlos.Get()) { bUseCoverVantagePoint = true; } #endif // __BANK //! Don't use vantage point if assisted aiming as it will slow things down as you won't actually hit the target //! if they are in cover. if(m_pPed->GetPlayerInfo()->IsAssistedAiming()) { bUseCoverVantagePoint = false; } if(bUseCoverVantagePoint) { iFlags |= CPedTargetEvaluator::TS_USE_COVER_VANTAGE_POINT_FOR_LOS; } if(CPedTargetEvaluator::ms_Tunables.m_UseNonNormalisedScoringForPlayer) { iFlags |= CPedTargetEvaluator::TS_USE_NON_NORMALIZED_SCORE; } u32 nTimeSinceAiming = fwTimer::GetTimeInMilliseconds() - m_uTimeSinceLastAiming; if(nTimeSinceAiming < CPedTargetEvaluator::ms_Tunables.m_TargetDistanceMaxWeightingAimTime) { iFlags |= CPedTargetEvaluator::TS_DO_COVER_LOCKON_REJECTION; } return iFlags; } bool CPlayerPedTargeting::FindAndSetLockOnTarget() { pedFatalAssertf(m_pPed, "m_pPed is NULL"); // Get our held weapon info const CWeaponInfo* pWeaponInfo = GetWeaponInfo(); if(!pWeaponInfo) { // No weapon return false; } // Do not allow lock-on targeting with static reticule if(pWeaponInfo->GetIsStaticReticulePosition()) { return false; } s32 iFlags = GetFindTargetFlags(); // Check if we're already locked on if(m_pLockOnTarget && !CPedTargetEvaluator::GetIsTargetingDisabled(m_pPed, m_pLockOnTarget, iFlags)) { // Cache the weapon range (also check to see if overriding from script) const float fWeaponRange = GetWeaponLockOnRange(pWeaponInfo, m_pLockOnTarget); // Find out if the target is still valid // Check if ped is in this weapon's range const Vector3 vLockOnTargetPos = VEC3V_TO_VECTOR3(m_pLockOnTarget->GetTransform().GetPosition()); const Vector3 vPedPosition = VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition()); Vector2 v2DPedDistance; v2DPedDistance.x = vLockOnTargetPos.x - vPedPosition.x; v2DPedDistance.y = vLockOnTargetPos.y - vPedPosition.y; float fPedDistance = v2DPedDistance.Mag(); if(fPedDistance > fWeaponRange) { ClearLockOnTarget(); } } else { // Prepare to find a new target CEntity* pTargetEntity = m_pCachedLockOnTarget ? m_pCachedLockOnTarget : FindLockOnTarget(); // Check if we found a valid target if(pTargetEntity) { // We found a target within weapon range - lock-on to it SetLockOnTarget(pTargetEntity); if(ShouldUsePullGunSpeech(pWeaponInfo)) m_pPed->NewSay("PULL_GUN"); } } return m_pLockOnTarget != NULL; } CEntity *CPlayerPedTargeting::FindLockOnTarget() { CEntity* pTargetEntity = NULL; const CWeaponInfo* pWeaponInfo = GetWeaponInfo(); if(pWeaponInfo) { //! DMKH. Don't do melee targeting in cover. if(pWeaponInfo->GetIsMelee() && !IsMeleeTargetingUsingNormalLock()) { pTargetEntity = FindLockOnMeleeTarget(m_pPed, true); } else { float fWeaponRange = GetWeaponLockOnRange(pWeaponInfo); bool bAllowNoReticule = true; if(pWeaponInfo->GetIsMelee()) { fWeaponRange = ms_Tunables.m_UnarmedInCoverTargetingDistance; bAllowNoReticule = false; } CPedTargetEvaluator::tHeadingLimitData headingData; float fPitchMin = 0.0f; float fPitchMax = 0.0f; float fDistanceWeighting = CPedTargetEvaluator::ms_Tunables.m_TargetDistanceWeightingMax; float fHeadingWeighting = CPedTargetEvaluator::ms_Tunables.m_TargetHeadingWeighting; float fDistanceFallOff = CPedTargetEvaluator::ms_Tunables.m_TargetDistanceFallOffMax; if( bAllowNoReticule && m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_NO_RETICULE_AIM_ASSIST_ON) ) { fWeaponRange *= ms_Tunables.GetNoReticuleLockOnRangeModifier(); fWeaponRange = Min(fWeaponRange, ms_Tunables.GetNoReticuleMaxLockOnRange()); headingData.fHeadingAngularLimit = ms_Tunables.GetNoReticuleTargetAngularLimit(); headingData.fHeadingAngularLimitClose = ms_Tunables.GetNoReticuleTargetAngularLimitClose(); headingData.fHeadingAngularLimitCloseDistMin = ms_Tunables.GetNoReticuleTargetAngularLimitCloseDistMin(); headingData.fHeadingAngularLimitCloseDistMax = ms_Tunables.GetNoReticuleTargetAngularLimitCloseDistMax(); fPitchMin = -ms_Tunables.GetNoReticuleTargetAngularLimitClose(); fPitchMax = ms_Tunables.GetNoReticuleTargetAngularLimitClose(); fDistanceWeighting = 1.0f; fHeadingWeighting = 1.0f; headingData.fAimPitchLimitMin = ms_Tunables.GetNoReticuleTargetAimPitchLimit(); headingData.fAimPitchLimitMax = ms_Tunables.GetNoReticuleTargetAimPitchLimit(); } else { fWeaponRange *= ms_Tunables.GetLockOnRangeModifier(); headingData.fHeadingAngularLimitClose = ms_Tunables.GetDefaultTargetAngularLimitClose(); headingData.fHeadingAngularLimitCloseDistMin = ms_Tunables.GetDefaultTargetAngularLimitCloseDistMin(); headingData.fHeadingAngularLimitCloseDistMax = ms_Tunables.GetDefaultTargetAngularLimitCloseDistMax(); u32 nTimeSinceAiming = fwTimer::GetTimeInMilliseconds() - m_uTimeSinceLastAiming; if(nTimeSinceAiming < CPedTargetEvaluator::ms_Tunables.m_TargetDistanceMaxWeightingAimTime) { float fWeight = (float)nTimeSinceAiming/(float)CPedTargetEvaluator::ms_Tunables.m_TargetDistanceMaxWeightingAimTime; float fMinWeighting = CPedTargetEvaluator::ms_Tunables.m_TargetDistanceWeightingMin; float fMaxWeighting = CPedTargetEvaluator::ms_Tunables.m_TargetDistanceWeightingMax; fDistanceWeighting = fMinWeighting + ((fMaxWeighting - fMinWeighting) * fWeight); float fMinFallOff = CPedTargetEvaluator::ms_Tunables.m_TargetDistanceFallOffMin; float fMaxFallOff = CPedTargetEvaluator::ms_Tunables.m_TargetDistanceFallOffMax; fDistanceFallOff = fMinFallOff + ((fMaxFallOff - fMinFallOff) * fWeight); } float fAngularRange = ms_Tunables.GetDefaultTargetAngularLimit(); if (m_pPed->GetIsDrivingVehicle() && m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON)) { TUNE_GROUP_FLOAT(DRIVE_BY_TUNE, DRIVEBY_ANGULAR_RANGE, 90.0f, 0.0f, 180.0f, 1.0f); fAngularRange = DRIVEBY_ANGULAR_RANGE; } headingData.fHeadingAngularLimit = fAngularRange; headingData.fWideHeadingAngulerLimit = ms_Tunables.GetWideTargetAngularLimit(); headingData.fAimPitchLimitMin = ms_Tunables.GetDefaultTargetAimPitchMin(); headingData.fAimPitchLimitMax = ms_Tunables.GetDefaultTargetAimPitchMax(); } pTargetEntity = CPedTargetEvaluator::FindTarget(m_pPed, GetFindTargetFlags(), fWeaponRange, fWeaponRange * ms_Tunables.GetLockOnDistanceRejectionModifier(), NULL, NULL, headingData, fPitchMin, fPitchMax, fDistanceWeighting, fHeadingWeighting, fDistanceFallOff); } } return pTargetEntity; } CEntity *CPlayerPedTargeting::FindLockOnMeleeTarget(const CPed *pPed, bool bAllowLockOnIfTargetFriendly) { const CWeaponInfo* pWeaponInfo = GetWeaponInfo(); if(!pWeaponInfo) { return NULL; } float fWeaponRange = GetWeaponLockOnRange(pWeaponInfo); s32 nDefaultFlags = CPedTargetEvaluator::MELEE_TARGET_FLAGS; CPedTargetEvaluator::tHeadingLimitData headingData; CControl* pControl = const_cast(pPed->GetControlFromPlayer()); if( pControl ) { // Check the desired direction input const Vector2 vecStickInput( pControl->GetPedWalkLeftRight().GetNorm(), -pControl->GetPedWalkUpDown().GetNorm() ); const float fInputMag = vecStickInput.Mag2(); if( fInputMag > rage::square( 0.5f ) ) { nDefaultFlags |= CPedTargetEvaluator::TS_DESIRED_DIRECTION_HEADING; nDefaultFlags |= CPedTargetEvaluator::TS_CAMERA_HEADING; headingData.fHeadingAngularLimit = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetAngularLimitMelee; headingData.fStickHeadingWeighting = CPedTargetEvaluator::ms_Tunables.m_MeleeLockOnStickWeighting; headingData.fCameraHeadingWeighting = CPedTargetEvaluator::ms_Tunables.m_MeleeLockOnCameraWeighting; } else { nDefaultFlags |= CPedTargetEvaluator::TS_CAMERA_HEADING; nDefaultFlags |= CPedTargetEvaluator::TS_PED_HEADING; headingData.fPedHeadingWeighting = CPedTargetEvaluator::ms_Tunables.m_MeleeLockOnCameraWeightingNoStick; headingData.fCameraHeadingWeighting = CPedTargetEvaluator::ms_Tunables.m_MeleeLockOnPedWeightingNoStick; headingData.bPedHeadingNeedsTargetOnScreen = true; headingData.fHeadingAngularLimit = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetAngularLimitMeleeLockOnNoStick; } } //! Allows us to lock on to peds that are allowing friendly lock on (e.g. chop). if(bAllowLockOnIfTargetFriendly) { nDefaultFlags |= CPedTargetEvaluator::TS_ALLOW_LOCK_ON_IF_TARGET_IS_FRIENDLY; } headingData.fHeadingFalloffPower = 1.0f; CEntity *pTargetEntity = CPedTargetEvaluator::FindTarget( pPed, nDefaultFlags, fWeaponRange, fWeaponRange, NULL, NULL, headingData, 0.0f, 0.0f, CPedTargetEvaluator::ms_Tunables.m_DefaultTargetDistanceWeightMelee, CPedTargetEvaluator::ms_Tunables.m_DefaultTargetHeadingWeightMelee ); return pTargetEntity; } CEntity *CPlayerPedTargeting::FindMeleeTarget(const CPed *pPed, const CWeaponInfo* pWeaponInfo, bool bDoMeleeActionCheck, bool bTargetDeadPeds, bool bFallBackToPedHeading, bool bMustUseDesiredDirection, const CEntity* pCurrentMeleeTarget) { // Find range of current weapon CPedTargetEvaluator::tHeadingLimitData headingData; headingData.fHeadingAngularLimit = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetAngularLimitMelee; headingData.fHeadingFalloffPower = 1.0f; float fPitchLimitMin = 0.0f; float fPitchLimitMax = 0.0f; float fWeaponRange = CPedTargetEvaluator::ms_Tunables.m_DefaultMeleeRange; // Default to the unarmed range. if( pWeaponInfo ) { const CAimingInfo* pAimingInfo = pWeaponInfo->GetAimingInfo(); if( pAimingInfo ) { headingData.fHeadingAngularLimit = pAimingInfo->GetHeadingLimit(); fPitchLimitMin = pAimingInfo->GetSweepPitchMin(); fPitchLimitMax = pAimingInfo->GetSweepPitchMax(); } fWeaponRange = pWeaponInfo->GetLockOnRange(); } float fHeadingWeighting = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetHeadingWeightMelee; float fDistanceWeighting = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetDistanceWeightMelee; #if FPS_MODE_SUPPORTED bool bIsFPSMode = pPed->IsFirstPersonShooterModeEnabledForPlayer(false); if( bIsFPSMode ) { fHeadingWeighting = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetHeadingWeightMeleeFPS; fDistanceWeighting = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetDistanceWeightMeleeFPS; } #endif if(CPedTargetEvaluator::ms_Tunables.m_UseMeleeHeadingOverride) { float fRunningScale = 0.0; if(pPed->GetMotionData()->GetCurrentMbrY() > MOVEBLENDRATIO_WALK) { fRunningScale = Min(pPed->GetMotionData()->GetCurrentMbrY() - MOVEBLENDRATIO_WALK / 1.0f, 1.0f); } headingData.fHeadingAngularLimit = CPedTargetEvaluator::ms_Tunables.m_MeleeHeadingOverride + ( (CPedTargetEvaluator::ms_Tunables.m_MeleeHeadingOverrideRunning - CPedTargetEvaluator::ms_Tunables.m_MeleeHeadingOverride) * fRunningScale ); headingData.fHeadingFalloffPower = 1.0f + ((CPedTargetEvaluator::ms_Tunables.m_MeleeHeadingFalloffPowerRunning-1.0f) * fRunningScale); #if FPS_MODE_SUPPORTED if( bIsFPSMode ) { fHeadingWeighting = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetHeadingWeightMeleeFPS + ((CPedTargetEvaluator::ms_Tunables.m_DefaultTargetHeadingWeightMeleeRunningFPS-CPedTargetEvaluator::ms_Tunables.m_DefaultTargetHeadingWeightMeleeFPS) * fRunningScale);; fDistanceWeighting = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetDistanceWeightMeleeFPS + ((CPedTargetEvaluator::ms_Tunables.m_DefaultTargetDistanceWeightMeleeRunningFPS-CPedTargetEvaluator::ms_Tunables.m_DefaultTargetDistanceWeightMeleeFPS) * fRunningScale); } else #endif { fHeadingWeighting = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetHeadingWeightMelee + ((CPedTargetEvaluator::ms_Tunables.m_DefaultTargetHeadingWeightMeleeRunning-CPedTargetEvaluator::ms_Tunables.m_DefaultTargetHeadingWeightMelee) * fRunningScale); fDistanceWeighting = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetDistanceWeightMelee + ((CPedTargetEvaluator::ms_Tunables.m_DefaultTargetDistanceWeightMeleeRunning-CPedTargetEvaluator::ms_Tunables.m_DefaultTargetDistanceWeightMelee) * fRunningScale); } } if(CPedTargetEvaluator::ms_Tunables.m_LimitMeleeRangeToDefault && (fWeaponRange > CPedTargetEvaluator::ms_Tunables.m_DefaultMeleeRange) ) { fWeaponRange = CPedTargetEvaluator::ms_Tunables.m_DefaultMeleeRange; } CEntity *pTargetEntity = NULL; s32 nDefaultFlags = CPedTargetEvaluator::MELEE_TARGET_FLAGS | CPedTargetEvaluator::TS_USE_MELEE_LOS_POSITION; if(bDoMeleeActionCheck) { nDefaultFlags |= CPedTargetEvaluator::TS_CALCULATE_MELEE_ACTION; } if(bTargetDeadPeds) { nDefaultFlags |= CPedTargetEvaluator::TS_ACCEPT_INJURED_AND_DEAD_PEDS; } CPedTargetEvaluator::tFindTargetParams testParams; CControl* pControl = const_cast(pPed->GetControlFromPlayer()); if( pControl ) { // Check the desired direction input const Vector2 vecStickInput( pControl->GetPedWalkLeftRight().GetNorm(), -pControl->GetPedWalkUpDown().GetNorm() ); const float fInputMag = vecStickInput.Mag2(); if( fInputMag > rage::square( 0.5f ) ) { nDefaultFlags |= CPedTargetEvaluator::TS_DESIRED_DIRECTION_HEADING; } else { //! If no stick input, see if we can use cached stick input if(fwTimer::GetTimeInMilliseconds() < (m_LastTimeDesiredStickInputSet + ms_Tunables.m_TimeToAllowCachedStickInputForMelee)) { const float fInputMag = m_vLastValidStickInput.Mag2(); if( fInputMag > rage::square( 0.5f ) ) { testParams.pvDesiredStickOverride = &m_vLastValidStickInput; nDefaultFlags |= CPedTargetEvaluator::TS_DESIRED_DIRECTION_HEADING; } } } } if(!(nDefaultFlags & CPedTargetEvaluator::TS_DESIRED_DIRECTION_HEADING)) { //! If we have no stick input, and we are asking for it, then bail, we aren't allowed to find a target. if(bMustUseDesiredDirection) { return NULL; } //! If we want to fallback to ped heading (as opposed to 360), then do so only if we aren't choosing stick direction. if( bFallBackToPedHeading ) { nDefaultFlags |= CPedTargetEvaluator::TS_PED_HEADING; } #if FPS_MODE_SUPPORTED //! If we're in first-person mode then ensure we use the camera heading. if( bIsFPSMode ) { nDefaultFlags |= CPedTargetEvaluator::TS_CAMERA_HEADING; } #endif } //! Force camera heading underwater. Need to use some form of distance scoring. CTaskMotionBase *pMotionTask = pPed->GetCurrentMotionTask(); bool bUnderWater = pMotionTask && pMotionTask->IsUnderWater(); if(bUnderWater) { nDefaultFlags |= CPedTargetEvaluator::TS_CAMERA_HEADING; } testParams.pTargetingPed = pPed; testParams.iFlags = nDefaultFlags; testParams.fMaxDistance = fWeaponRange; testParams.fMaxRejectionDistance = fWeaponRange; testParams.headingLimitData = headingData; testParams.fPitchLimitMin = fPitchLimitMin; testParams.fPitchLimitMax = fPitchLimitMax; testParams.fDistanceWeight = fDistanceWeighting; testParams.fHeadingWeight = fHeadingWeighting; testParams.pCurrentMeleeTarget = pCurrentMeleeTarget; // First try to find a target that this player is attempting to attack based on controller stick direction pTargetEntity = CPedTargetEvaluator::FindTarget( testParams ); return pTargetEntity; } bool CPlayerPedTargeting::FindNextLockOnTarget(CEntity* pOldTarget, HeadingDirection eDirection, bool bSetTarget) { // Get our held weapon info const CWeaponInfo* pWeaponInfo = GetWeaponInfo(); if(!pWeaponInfo) { // No weapon return false; } // Do not allow lock-on targeting with static reticule if(pWeaponInfo->GetIsStaticReticulePosition()) { return false; } float fLockOnRange = GetWeaponLockOnRange(pWeaponInfo); s32 iFlags = 0; // Allow auto targeting of players in vehicles if we're on foot if (NetworkInterface::IsGameInProgress() && !m_pPed->GetIsInVehicle()) { iFlags |= CPedTargetEvaluator::TS_LOCKON_TO_PLAYERS_IN_VEHICLES; } CPedTargetEvaluator::tHeadingLimitData headingData; float fDistanceWeight; float fHeadingWeight; fDistanceWeight = CPedTargetEvaluator::ms_Tunables.m_TargetDistanceWeightingMax; fHeadingWeight = CPedTargetEvaluator::ms_Tunables.m_TargetHeadingWeighting; if(pWeaponInfo->GetIsMelee() && !IsMeleeTargetingUsingNormalLock()) { iFlags |= CPedTargetEvaluator::MELEE_TARGET_FLAGS; iFlags |= CPedTargetEvaluator::TS_CAMERA_HEADING; headingData.fHeadingAngularLimit = ms_Tunables.GetCycleTargetAngularLimitMelee(); headingData.fHeadingFalloffPower = 1.0f; #if FPS_MODE_SUPPORTED if(m_pPed->IsFirstPersonShooterModeEnabledForPlayer(false)) { fDistanceWeight = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetDistanceWeightMeleeFPS; fHeadingWeight = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetHeadingWeightMeleeFPS; } else #endif { fDistanceWeight = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetDistanceWeightMelee; fHeadingWeight = CPedTargetEvaluator::ms_Tunables.m_DefaultTargetHeadingWeightMelee; } headingData.bCamHeadingNeedsTargetOnScreen = true; iFlags |= ((eDirection == HD_LEFT) ? CPedTargetEvaluator::TS_CYCLE_LEFT : CPedTargetEvaluator::TS_CYCLE_RIGHT); } else { if(pWeaponInfo->GetIsMelee()) { fLockOnRange = ms_Tunables.m_UnarmedInCoverTargetingDistance; } headingData.fHeadingAngularLimit = ms_Tunables.GetCycleTargetAngularLimit(); headingData.fWideHeadingAngulerLimit = ms_Tunables.GetWideTargetAngularLimit(); headingData.fAimPitchLimitMin = ms_Tunables.GetDefaultTargetAimPitchMin(); headingData.fAimPitchLimitMax = ms_Tunables.GetDefaultTargetAimPitchMax(); iFlags |= ((eDirection == HD_LEFT) ? CPedTargetEvaluator::CYCLE_LEFT_TARGET_FLAGS : CPedTargetEvaluator::CYCLE_RIGHT_TARGET_FLAGS); #if FPS_MODE_SUPPORTED //! In 1st person, don't allow us to lock on to peds that are off screen. if(m_pPed->IsFirstPersonShooterModeEnabledForPlayer(false)) { headingData.bCamHeadingNeedsTargetOnScreen = true; } #endif } float fRejectionRange = fLockOnRange * ms_Tunables.GetLockOnDistanceRejectionModifier(); CEntity* pTargetEntity = pTargetEntity = CPedTargetEvaluator::FindTarget(m_pPed, iFlags, fLockOnRange, fRejectionRange, pOldTarget, NULL, headingData, 0.0f, 0.0f, fDistanceWeight, fHeadingWeight); if(bSetTarget && pTargetEntity && pTargetEntity != pOldTarget) { SetLockOnTarget(pTargetEntity, true); } return m_pLockOnTarget != NULL; } bool CPlayerPedTargeting::GetShouldBreakSoftLockTarget() { if(m_pLockOnTarget && m_pLockOnTarget->GetIsTypePed()) { const CPed* pTargetPed = static_cast(m_pLockOnTarget.Get()); const CWeaponInfo* pWeaponInfo = GetWeaponInfo(); //! Break if ped does a combat roll. if(pTargetPed->GetPedResetFlag(CPED_RESET_FLAG_BreakTargetLock) && (!pWeaponInfo || !pWeaponInfo->GetIsMelee())) { return true; } } return false; } bool CPlayerPedTargeting::GetShouldBreakLockOnWithTarget() { pedFatalAssertf(m_pPed, "m_pPed is NULL"); if(m_pLockOnTarget) { bool bForceLockOnUpdate = false; if (m_pPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON)) { TUNE_GROUP_BOOL(PLAYER_TARGETING, ENABLE_ASSISTED_ANGLE_CHECK, false); if (ENABLE_ASSISTED_ANGLE_CHECK) { Vector3 vecAim = camInterface::GetPlayerControlCamAimFrame().GetFront(); vecAim.z = 0.0f; vecAim.Normalize(); Vector3 vToTarget = VEC3V_TO_VECTOR3(m_pLockOnTarget->GetTransform().GetPosition() - m_pPed->GetTransform().GetPosition()); vToTarget.z = 0.0f; vToTarget.Normalize(); const float fDot = vecAim.Dot(vToTarget); TUNE_GROUP_FLOAT(PLAYER_TARGETING, dotTolerance, 0.85f, 0.0f, 1.0f, 0.01f); if (fDot > dotTolerance) bForceLockOnUpdate = true; } else { bForceLockOnUpdate = true; } } if(!CPlayerInfo::IsHardAiming() && !CPlayerInfo::IsDriverFiring() && !bForceLockOnUpdate) { // Release lock if the lockon isn't held down hard return true; } const CWeaponInfo* pWeaponInfo = GetWeaponInfo(); if(!pWeaponInfo) { // No weapon return true; } CWeapon* pWeapon = m_pPed->GetWeaponManager()->GetEquippedWeapon(); bool bCanLockon = CanPedLockOnWithWeapon(pWeapon, *pWeaponInfo); if(!bCanLockon) { // Weapon can't lock-on return true; } s32 iFlags = CPedTargetEvaluator::DEFAULT_TARGET_FLAGS; if(pWeaponInfo->GetIsMelee() && !IsMeleeTargetingUsingNormalLock()) { iFlags = CPedTargetEvaluator::MELEE_TARGET_FLAGS; //! If we lose LOS, break lock. if(GetShouldBreakLockTargetIfNoLOS(iFlags, ms_Tunables.m_MeleeLostLOSBreakTime, 250)) { return true; } } // This will test to see if the current target is still a valid lockon target if(CPedTargetEvaluator::GetIsTargetingDisabled(m_pPed, m_pLockOnTarget, iFlags)) { // Targeting disabled return true; } if(m_pLockOnTarget->GetIsTypePed()) { const CPed* pTargetPed = static_cast(m_pLockOnTarget.Get()); if( pWeaponInfo->GetIsMelee() ) { // Are they dead or dying if( !pTargetPed->GetTargetWhenInjuredAllowed() && pTargetPed->GetIsDeadOrDying() ) { return true; } } // Allow auto targeting of players in vehicles if we're on foot if (NetworkInterface::IsGameInProgress() && !m_pPed->GetIsInVehicle()) { iFlags |= CPedTargetEvaluator::TS_LOCKON_TO_PLAYERS_IN_VEHICLES; } if(!CPedTargetEvaluator::GetCanPedBeTargetedInAVehicle(m_pPed, pTargetPed, iFlags)) { // Can't target ped in a vehicle return true; } if(!CLocalisation::KickingWhenDown()) { if(pTargetPed->GetIsDeadOrDying()) { // Break lock-on when target is dead due to localisation reasons return true; } } //! Break if ped does a combat roll. if(pTargetPed->GetPedResetFlag(CPED_RESET_FLAG_BreakTargetLock) && !pWeaponInfo->GetIsMelee()) { return true; } } // Check if we have gone outside of lock-on range const Vector3 vPedPosition = VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition()); const Vector3 vLockOnTargetPosition = VEC3V_TO_VECTOR3(m_pLockOnTarget->GetTransform().GetPosition()); Vector3 vDistance = vLockOnTargetPosition - vPedPosition; float fDistanceSq = vDistance.Mag2(); const float fLockOnRange = GetWeaponLockOnRange(pWeaponInfo, m_pLockOnTarget); if(fDistanceSq > rage::square(fLockOnRange)) { return true; } if(!pWeaponInfo->GetIsMelee() || IsMeleeTargetingUsingNormalLock()) { // Test if the aiming IK can reach the target position // PH: Do the IK test against the root bone, primarily because NM // can drop the target position and break lock-on even though the reticule position is valid Vector3 vTargetPos = vLockOnTargetPosition; if(m_pLockOnTarget->GetIsTypePed()) { const CPed* pPedTarget = static_cast(m_pLockOnTarget.Get()); s32 iBoneIndex = pPedTarget->GetBoneIndexFromBoneTag(BONETAG_ROOT); Matrix34 globalBoneMat; pPedTarget->GetGlobalMtx(iBoneIndex, globalBoneMat); vTargetPos = globalBoneMat.d; } Vector3 vDelta = vTargetPos - vPedPosition; vDelta.Normalize(); // Calculate and limit the pitch float fPitch = rage::Atan2f(vDelta.z, vDelta.XYMag()); fPitch = fwAngle::LimitRadianAngle(fPitch); if(fPitch > CTorsoIkSolver::ms_torsoInfo.maxPitch || fPitch < CTorsoIkSolver::ms_torsoInfo.minPitch) { // IK can't reach target return true; } } } return false; } bool CPlayerPedTargeting::GetShouldBreakLockTargetIfNoLOS(s32 iFlags, float fBreakTime, u32 nTestTimeMS) { pedFatalAssertf(m_pPed, "m_pPed is NULL"); if(m_pLockOnTarget) { if(CPedTargetEvaluator::GetNeedsLineOfSightToTarget(m_pLockOnTarget)) { if( (m_fLostLockLOSTimer > 0.0f) || ((fwTimer::GetTimeInMilliseconds() - nTestTimeMS) > m_uLastLockLOSTestTime) ) { m_uLastLockLOSTestTime = fwTimer::GetTimeInMilliseconds(); bool bLostLOS = CPedTargetEvaluator::GetIsLineOfSightBlocked(m_pPed, m_pLockOnTarget, false, //bIgnoreTargetsCover (iFlags & CPedTargetEvaluator::TS_IGNORE_LOW_OBJECTS)!=0, //bAlsoIgnoreLowObjects (iFlags & CPedTargetEvaluator::TS_USE_MODIFIED_LOS_DISTANCE_IF_SET) != 0, //bUseTargetableDistance (iFlags & CPedTargetEvaluator::TS_IGNORE_VEHICLES_IN_LOS) != 0, //bIgnoreVehiclesForLOS (iFlags & CPedTargetEvaluator::TS_USE_COVER_VANTAGE_POINT_FOR_LOS)!=0, //bUseCoverVantagePointForLOS (iFlags & CPedTargetEvaluator::TS_USE_MELEE_LOS_POSITION)!=0, //bUseMeleePosForLOS false, //bTestPotentialPedTargets iFlags); if(bLostLOS) { m_fLostLockLOSTimer += fwTimer::GetTimeStep(); if(m_fLostLockLOSTimer > fBreakTime) return true; } else { m_fLostLockLOSTimer = 0.0f; } } } } return false; } void CPlayerPedTargeting::UpdateFineAim(bool bSpringback) { pedFatalAssertf(m_pPed, "m_pPed is NULL"); if(m_pLockOnTarget) { float fTimeStep = fwTimer::GetTimeStep_NonScaledClipped(); float fVerticalAimMovement = ms_Tunables.GetFineAimVerticalMovement(); float fDownwardsVerticalAimMovement = ms_Tunables.GetFineAimDownwardsVerticalMovement(); float fSidewaysScale = ms_Tunables.GetFineAimSidewaysScale(); weaponAssert(m_pPed->GetWeaponManager()); if(m_pPed->GetWeaponManager()->GetIsArmedMelee()) { // Can't fine aim when using melee weapons ClearFineAim(); } else { const CControl* pControl = m_pPed->GetControlFromPlayer(); if(pControl && !IsTaskDisablingTargeting()) { Vector3 vFineAimOffset(Vector3::ZeroType); bool bAttemptingFineAim = false; static dev_float MIN_FINE_AIM_VEC = 0.05f; if(!m_bPlayerIsFineAiming) { //! Clear any offset till we allow fine aiming. m_vFineAimOffset.Zero(); } if(bSpringback) { vFineAimOffset.x = pControl->GetPedAimWeaponLeftRight().GetUnboundNorm() * m_pPed->GetCapsuleInfo()->GetHalfWidth() * fSidewaysScale; vFineAimOffset.z = -1.0f * pControl->GetPedAimWeaponUpDown().GetUnboundNorm(); if(vFineAimOffset.z > 0.0f) { vFineAimOffset.z *= fVerticalAimMovement; } else { vFineAimOffset.z *= fDownwardsVerticalAimMovement; } bAttemptingFineAim = vFineAimOffset.Mag2() > (rage::square(MIN_FINE_AIM_VEC)); } else { Vector3 vLockOnPos = VEC3V_TO_VECTOR3(m_pLockOnTarget->GetTransform().GetPosition()); Vector3 vPedPos = VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition()); Vector3 vDistance = vLockOnPos - vPedPos; float fDist = Clamp(vDistance.Mag(), ms_Tunables.GetFineAimSpeedMultiplierCloseDistMin(), ms_Tunables.GetFineAimSpeedMultiplierCloseDistMax()); float fSpeedWeight = (fDist - ms_Tunables.GetFineAimSpeedMultiplierCloseDistMin()) / (ms_Tunables.GetFineAimSpeedMultiplierCloseDistMax() - ms_Tunables.GetFineAimSpeedMultiplierCloseDistMin()); fSpeedWeight = Clamp(fSpeedWeight, 0.0f, 1.0f); float fSpeedMultiplier = ms_Tunables.GetFineAimSpeedMultiplierClose() + ((1.0f - ms_Tunables.GetFineAimSpeedMultiplierClose()) * fSpeedWeight); fSpeedMultiplier *= ms_Tunables.GetFineAimSpeedMultiplier(); fSpeedMultiplier *= fTimeStep; vFineAimOffset = m_vFineAimOffset; if(abs(pControl->GetPedAimWeaponLeftRight().GetUnboundNorm()) > 0.99f) { m_fFineAimHorizontalSpeedWeight += ms_Tunables.GetFineAimHorWeightSpeedMultiplier() * fTimeStep; m_fFineAimHorizontalSpeedWeight = Min(m_fFineAimHorizontalSpeedWeight, 1.0f); } else { m_fFineAimHorizontalSpeedWeight -= ms_Tunables.GetFineAimHorWeightSpeedMultiplier() * fTimeStep; m_fFineAimHorizontalSpeedWeight = Max(m_fFineAimHorizontalSpeedWeight, 0.0f); } float fFineAimHorizontalSpeedWeight = rage::Powf(m_fFineAimHorizontalSpeedWeight, ms_Tunables.GetFineAimHorSpeedPower()); float fHorSpeed = ms_Tunables.GetFineAimHorSpeedMin() + ((ms_Tunables.GetFineAimHorSpeedMax() - ms_Tunables.GetFineAimHorSpeedMin()) * fFineAimHorizontalSpeedWeight); vFineAimOffset.x += pControl->GetPedAimWeaponLeftRight().GetUnboundNorm() * fHorSpeed * fSpeedMultiplier; vFineAimOffset.z += -1.0f * pControl->GetPedAimWeaponUpDown().GetUnboundNorm() * ms_Tunables.GetFineAimVerSpeed() * fSpeedMultiplier; //! Start attempting to fine aim immediately. m_bHasAttemptedNonSpringFineAim = true; bAttemptingFineAim = m_bHasAttemptedNonSpringFineAim; } if(!m_bPlayerIsFineAiming) { if(bAttemptingFineAim) { m_fTimeFineAiming += fTimeStep; } else { m_fTimeFineAiming = 0.0f; } float fUpDown = pControl->GetPedAimWeaponUpDown().GetUnboundNorm(); static dev_float s_fReleaseThreshold = 0.1f; static dev_float s_fUpDownThreshold = 0.75f; if( (abs(fUpDown) < s_fReleaseThreshold)) { m_bPlayerReleasedUpDownControlForFineAim = true; } if(m_bPlayerReleasedUpDownControlForFineAim && (abs(fUpDown) > s_fUpDownThreshold)) { m_bPlayerPressingUpDownControlForFineAim = true; } float fMinTime = m_bPlayerPressingUpDownControlForFineAim ? ms_Tunables.GetMinFineAimTime() : ms_Tunables.GetMinFineAimTimeHoldingStick(); const camGameplayDirector& gameplayDirector = camInterface::GetGameplayDirector(); bool bShowingReticule = gameplayDirector.ShouldDisplayReticule() || CNewHud::IsReticuleVisible(); bool bAllowReticuleToEnableFineAiming = false; if(m_fTimeFineAiming > ms_Tunables.GetMinFineAimTime()) { bAllowReticuleToEnableFineAiming = bShowingReticule; } if( (m_fTimeFineAiming > fMinTime) || bAllowReticuleToEnableFineAiming) { m_bPlayerIsFineAiming = true; } } else { if(bSpringback) { static dev_float LERP_AMOUNT = 0.5f; m_vFineAimOffset.Lerp(LERP_AMOUNT, vFineAimOffset); } else { m_vFineAimOffset = vFineAimOffset; } m_fTimeFineAiming += fTimeStep; } } } } else { // No lock-on target ClearFineAim(); } } void CPlayerPedTargeting::ClearFineAim() { m_bPlayerIsFineAiming = false; m_fTimeFineAiming = 0.0f; m_fFineAimHorizontalSpeedWeight = 0.0f; m_bHasAttemptedNonSpringFineAim = false; m_bPlayerReleasedUpDownControlForFineAim = false; m_bPlayerPressingUpDownControlForFineAim = false; } void CPlayerPedTargeting::ClearSoftAim() { m_bPlayerHasStartedToAim = false; m_bPlayerHasBrokenLockOn = false; m_fTimeSoftLockedOn = 0.0f; } void CPlayerPedTargeting::ClearFreeAim() { // Clear the pointers m_pFreeAimTarget = NULL; m_pFreeAimTargetRagdoll = NULL; m_pFreeAimAssistTarget = NULL; m_uStartTimeAimingAtFreeAimTarget = 0; m_bFreeAimTargetingVehicleForCamera = false; m_FreeAimProbeResults.Reset(); //m_FreeAimAssistProbeResults.Reset(); // Should only be cleared in CPlayerPedTargeting::FindFreeAimAssistTarget } void CPlayerPedTargeting::FindFreeAimTarget(const Vector3& vStart, const Vector3& vEnd, const CWeaponInfo& weaponInfo, Vector3& vHitPos) { pedFatalAssertf(m_pPed, "m_pPed is NULL"); bool bRejectFreeAimResults = false; #if FPS_MODE_SUPPORTED if( m_pPed->IsFirstPersonShooterModeEnabledForPlayer(false) ) { if( weaponInfo.GetIsArmed() ) { if( m_pPed->GetPlayerInfo() && !m_pPed->GetPlayerInfo()->IsAiming() ) { bRejectFreeAimResults = true; } } else { // In first person, cannot aim if unarmed. bRejectFreeAimResults = true; } } #endif // FPS_MODE_SUPPORTED CVehicle *pVehicleTargeterInside = m_pPed->GetVehiclePedInside(); bool bInLargeVehicle = pVehicleTargeterInside && (pVehicleTargeterInside->GetSeatManager()->GetMaxSeats() > 8); //static dev_bool sbForceSychronous = false; bool bUseSynchronousResults = false; if (/*sbForceSychronous ||*/ m_pPed->GetIsAttached()) { // Probe for a target m_FreeAimProbeResults.Reset(); WorldProbe::CShapeTestProbeDesc probeDesc; probeDesc.SetResultsStructure(&m_FreeAimProbeResults); probeDesc.SetStartAndEnd(vStart, vEnd); probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_RAGDOLL_TYPE | ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_ALL_MAP_TYPES | ArchetypeFlags::GTA_OBJECT_TYPE | ArchetypeFlags::GTA_GLASS_TYPE); probeDesc.SetExcludeEntity(m_pPed, bInLargeVehicle || m_pPed->IsInFirstPersonVehicleCamera() ? WorldProbe::EIEO_DONT_ADD_VEHICLE_OCCUPANTS : EXCLUDE_ENTITY_OPTIONS_NONE); WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc, WorldProbe::PERFORM_SYNCHRONOUS_TEST); bUseSynchronousResults = true; } #if RSG_PC bool bStereoEnabled = GRCDEVICE.IsStereoEnabled() && GRCDEVICE.CanUseStereo(); const camFrame& aimFrame = camInterface::GetPlayerControlCamAimFrame(); Vector3 vStartCam = aimFrame.GetPosition(); #endif if(m_FreeAimProbeResults.GetResultsReady() || bUseSynchronousResults) { CEntity* pFreeAimTarget = NULL; CEntity* pFreeAimTargetRagdoll = NULL; m_vClosestFreeAimTargetPos.Zero(); Vector3 vClosestFreeAimTargetPos(Vector3::ZeroType); bool bFreeAimTargetingVehicleForCamera = false; if(m_FreeAimProbeResults.GetNumHits() > 0) { const CEntity* pHitEntity = m_FreeAimProbeResults[0].GetHitEntity(); if(pHitEntity && pHitEntity->GetIsTypeVehicle()) { bFreeAimTargetingVehicleForCamera = true; } } #if RSG_PC if (bStereoEnabled) { m_vClosestFreeAimTargetPosStereo.Zero(); } #endif for(WorldProbe::ResultIterator i = m_FreeAimProbeResults.begin(); i < m_FreeAimProbeResults.last_result(); ++i) { const bool bIsShootThrough = PGTAMATERIALMGR->GetMtlFlagShootThrough( i->GetHitMaterialId() ) || PGTAMATERIALMGR->GetIsShootThrough( i->GetHitMaterialId() ); if (bIsShootThrough ) continue; //Remember closest hit position Vector3 hitPos = i->GetHitPosition(); if (vClosestFreeAimTargetPos.IsZero() || (hitPos - vStart).Mag2() < (vClosestFreeAimTargetPos-vStart).Mag2() ) vClosestFreeAimTargetPos = hitPos; #if RSG_PC if (bStereoEnabled) { if (m_vClosestFreeAimTargetPosStereo.IsZero() || (hitPos - vStartCam).Mag2() < (m_vClosestFreeAimTargetPosStereo-vStartCam).Mag2() ) { m_vClosestFreeAimTargetPosStereo = hitPos; m_vFreeAimTargetDir = (m_vClosestFreeAimTargetPosStereo-vStartCam); m_fClosestFreeAimTargetDist = m_vFreeAimTargetDir.Mag(); m_vFreeAimTargetDir.Normalize(); } } #endif CEntity* pEntityAimedAt = i->GetHitEntity(); if(pEntityAimedAt) { if(pEntityAimedAt->GetIsTypePed()) { CPed* pPedAimedAt = static_cast(pEntityAimedAt); //! Don't allow free aiming against anyone inside our own vehicle. Just skip. //! Note: in 1st person, we allow this so you can't (so obviously) shoot through friendly peds. if(!m_pPed->IsInFirstPersonVehicleCamera()) { if(pVehicleTargeterInside && (pVehicleTargeterInside == pPedAimedAt->GetVehiclePedInside())) { continue; } } //! If we hit ragdoll, set both. if(pPedAimedAt->GetRagdollInst() == i->GetHitInst()) { pFreeAimTarget = pPedAimedAt; pFreeAimTargetRagdoll = pPedAimedAt; } else { // Otherwise, go through list (from current point) and check if we hit ragdoll too. WorldProbe::ResultIterator j = i; for(; j < m_FreeAimProbeResults.last_result(); ++j) { CEntity* pEntityAimedAtPostPed = j->GetHitEntity(); if(pEntityAimedAtPostPed) { if(pEntityAimedAtPostPed->GetIsTypePed()) { CPed *pPedAimedAtPostPed = static_cast(pEntityAimedAtPostPed); //! Note: if we have got this far it means that the capsule contact didn't actually hit //! ragdoll bounds. In that case, allow us to pick any ragdoll inst. if(pPedAimedAtPostPed->GetRagdollInst() == j->GetHitInst()) { pFreeAimTargetRagdoll = pPedAimedAtPostPed; pPedAimedAt = pPedAimedAtPostPed; break; } else { //hit animated bounds, just keep going. } } else if(pEntityAimedAtPostPed->GetIsTypeVehicle()) { if(pPedAimedAt->GetMyVehicle() != pEntityAimedAtPostPed) { break; } //If we hit vehicle aimed at ped is in, keep going to find ragdoll inst we are aiming at. } else { // Stop out if subsequent hit isn't the same entity break; } } } pFreeAimTarget = pPedAimedAt; } vHitPos = VEC3V_TO_VECTOR3(i->GetPosition()); break; } else if(pEntityAimedAt->GetIsTypeVehicle()) { CVehicle* pVehicleAimedAt = static_cast(pEntityAimedAt); //Note that the player aimed at the vehicle. if(m_pPed->GetVehiclePedInside() != pVehicleAimedAt) { pVehicleAimedAt->GetIntelligence()->PlayerAimedAtOrAround(); } // Check if we subsequently hit a ped WorldProbe::ResultIterator j = i; j++; for(; j < m_FreeAimProbeResults.last_result(); ++j) { CEntity* pEntityAimedAtPostVehicle = j->GetHitEntity(); if(pEntityAimedAtPostVehicle && pVehicleAimedAt != pEntityAimedAtPostVehicle) { if(pEntityAimedAtPostVehicle->GetIsTypePed() && static_cast(pEntityAimedAtPostVehicle)->GetVehiclePedInside() == pEntityAimedAt) { pFreeAimTarget = pEntityAimedAtPostVehicle; vHitPos = VEC3V_TO_VECTOR3(j->GetPosition()); break; } } } if (!pFreeAimTarget) { //! By default, choose vehicle. pFreeAimTarget = pVehicleAimedAt; vHitPos = VEC3V_TO_VECTOR3(i->GetPosition()); // See if we can override with a ped from inside vehicle. s32 iMaxSeats = pVehicleAimedAt->GetSeatManager()->GetMaxSeats(); for(s32 iSeat = 0; iSeat < iMaxSeats; iSeat++) { CPed* pPedInSeat = pVehicleAimedAt->GetSeatManager()->GetPedInSeat(iSeat); if(pPedInSeat) { pFreeAimTarget = pPedInSeat; break; } } } break; } else if (pEntityAimedAt->GetType() == ENTITY_TYPE_OBJECT || pEntityAimedAt->GetType() == ENTITY_TYPE_BUILDING) { pFreeAimTarget = pEntityAimedAt; break; } else { // Blocked break; } } } if(vClosestFreeAimTargetPos.Mag2() > 0.01f) { m_vClosestFreeAimTargetPos = vClosestFreeAimTargetPos; } else { m_vClosestFreeAimTargetPos = vEnd; } if(pFreeAimTarget && !bRejectFreeAimResults) { // We are aiming at something, but its not the thing we were aiming at last frame if(pFreeAimTarget != m_pFreeAimTarget) { m_pFreeAimTarget = pFreeAimTarget; m_uStartTimeAimingAtFreeAimTarget = fwTimer::GetTimeInMilliseconds(); if(ShouldUsePullGunSpeech(&weaponInfo)) m_pPed->NewSay("PULL_GUN"); } // Is it a ragdoll under the reticle? m_pFreeAimTargetRagdoll = pFreeAimTargetRagdoll; } else { // Clear free aim target if none is found vHitPos.Zero(); ClearFreeAim(); } //NOTE: This is deliberately assigned after any potential call to ClearFreeAim() above. m_bFreeAimTargetingVehicleForCamera = bFreeAimTargetingVehicleForCamera; } if(!bUseSynchronousResults && !m_FreeAimProbeResults.GetWaitingOnResults()) { m_FreeAimProbeResults.Reset(); // Probe for a target WorldProbe::CShapeTestProbeDesc probeDesc; probeDesc.SetResultsStructure(&m_FreeAimProbeResults); probeDesc.SetStartAndEnd(vStart, vEnd); probeDesc.SetIncludeFlags(ArchetypeFlags::GTA_RAGDOLL_TYPE | ArchetypeFlags::GTA_PED_TYPE | ArchetypeFlags::GTA_VEHICLE_TYPE | ArchetypeFlags::GTA_ALL_MAP_TYPES | ArchetypeFlags::GTA_OBJECT_TYPE | ArchetypeFlags::GTA_GLASS_TYPE); probeDesc.SetExcludeEntity(m_pPed, bInLargeVehicle ? WorldProbe::EIEO_DONT_ADD_VEHICLE_OCCUPANTS : EXCLUDE_ENTITY_OPTIONS_NONE); WorldProbe::GetShapeTestManager()->SubmitTest(probeDesc, WorldProbe::PERFORM_ASYNCHRONOUS_TEST); } } void CPlayerPedTargeting::FindFreeAimAssistTarget(const Vector3& vStartIn, const Vector3& vEndIn) { if (!ms_Tunables.GetEnableBulletBending()) { return; } pedFatalAssertf(m_pPed, "m_pPed is NULL"); Vector3 vStart = vStartIn; Vector3 vEnd = vEndIn; if (!ms_Tunables.m_DoAynchronousProbesWhenFindingFreeAimAssistTarget) { ProcessFreeAimAssistTargetCapsuleTests(vStart, vEnd); } if(m_FreeAimAssistProbeResults.GetResultsReady()) { CEntity* pClosestEntity = NULL; float fBestScore = FLT_MAX; Vector3 vIntersection; #if DEBUG_DRAW u32 i = 0; #endif // DEBUG_DRAW bool bNeedsTest = false; for(WorldProbe::ResultIterator it = m_FreeAimAssistProbeResults.begin(); it < m_FreeAimAssistProbeResults.last_result(); ++it) { #if DEBUG_DRAW if(ms_Tunables.m_DisplayAimAssistTest) { char buff[64]; formatf(buff, "DISPLAY_AIM_ASSIST_INTERSECTIONS_%d", i); CWeaponDebug::ms_debugStore.AddSphere(RCC_VEC3V(it->GetHitPosition()), 0.05f, Color_orange, 1000, atStringHash(buff)); } ++i; #endif // DEBUG_DRAW bool bHitPed = false; CEntity* pEntity = it->GetHitEntity(); if(pEntity) { if(pEntity->GetIsTypePed()) { bHitPed = true; CPed* pPed = static_cast(pEntity); pedAssertf(m_pPed != pPed, "IsAHit = %s, hit inst = %p, ragdoll inst = %p, animated inst = %p", it->IsAHit() ? "true" : "false", it->GetHitInst(), pPed->GetRagdollInst(), pPed->GetAnimatedInst()); if(GetIsPedValidForFreeAimAssist(pPed)) { float fScore = GetScoreForFreeAimAssistIntersection(vStart, vEnd, it->GetHitPosition()); if(fScore < fBestScore) { pClosestEntity = pEntity; fBestScore = fScore; vIntersection = it->GetHitPosition(); } } } else { //! hit something that wasn't a ped 1st. Need to do a LOS check to determine if the if(!bHitPed) { bNeedsTest = true; } } } } if(bNeedsTest && pClosestEntity) { Vector3 vTestPos = vIntersection; if(pClosestEntity->GetIsTypePed()) { CPed* pPed = static_cast(pClosestEntity); vTestPos.z = pPed->GetBonePositionCached(BONETAG_HEAD).z; } s32 iTargetOptions = 0; if(!CPedGeometryAnalyser::CanPedTargetPoint(*m_pPed, vTestPos, iTargetOptions)) { pClosestEntity = NULL; } } #if __BANK if(CPedTargetEvaluator::ms_Tunables.m_DebugTargetting && pClosestEntity) { CWeaponDebug::ms_debugStore.AddSphere(RCC_VEC3V(vIntersection), 0.05f, Color_purple, 1000, ATSTRINGHASH("FREE_AIM_ASSIST_TARGET_BEST_INTERSECTION", 0xB82FD8C0)); } #endif // __BANK m_vClosestFreeAimAssistTargetPos = vIntersection; // Attempt to get a more accurate target position (If using the new slow down code, still get the more accurate assist position) if(pClosestEntity && (GetMoreAccurateFreeAimAssistTargetPos(vStart, vEnd, vIntersection, pClosestEntity, m_vFreeAimAssistTargetPos) || ms_Tunables.GetUseNewSlowDownCode())) { m_pFreeAimAssistTarget = pClosestEntity; } else { // Clear the aim assist target m_pFreeAimAssistTarget = NULL; } } ProcessPedEventsForFreeAimAssist(); if (ms_Tunables.m_DoAynchronousProbesWhenFindingFreeAimAssistTarget) { ProcessFreeAimAssistTargetCapsuleTests(vStart, vEnd); } #if __BANK if(CPedTargetEvaluator::ms_Tunables.m_DebugTargetting && m_pFreeAimAssistTarget) { Vector3 vAssistTargetPosition = VEC3V_TO_VECTOR3(m_pFreeAimAssistTarget->GetTransform().GetPosition()); vAssistTargetPosition.z += 1.0f; CWeaponDebug::ms_debugStore.AddSphere(RCC_VEC3V(vAssistTargetPosition), 0.05f, Color_cyan, 1000, ATSTRINGHASH("FREE_AIM_ASSIST_TARGET", 0xD1F6DCE1)); } #endif // __BANK } void CPlayerPedTargeting::ProcessFreeAimAssistTargetCapsuleTests(const Vector3& vStart, const Vector3& vEnd) { if(!m_FreeAimAssistProbeResults.GetWaitingOnResults()) { m_FreeAimAssistProbeResults.Reset(); float fTestRadius = m_pPed->GetIsInVehicle() ? ms_Tunables.GetAimAssistCapsuleRadiusInVehicle() : ms_Tunables.GetAimAssistCapsuleRadius(); WorldProbe::eBlockingMode blockingMode = ms_Tunables.m_DoAynchronousProbesWhenFindingFreeAimAssistTarget ? WorldProbe::PERFORM_ASYNCHRONOUS_TEST : WorldProbe::PERFORM_SYNCHRONOUS_TEST; // Perform a capsule check with ped's, this will give us the free aim target WorldProbe::CShapeTestCapsuleDesc pedCapsuleDesc; pedCapsuleDesc.SetResultsStructure(&m_FreeAimAssistProbeResults); pedCapsuleDesc.SetCapsule(vStart, vEnd, fTestRadius); s32 flags = ArchetypeFlags::GTA_RAGDOLL_TYPE | ArchetypeFlags::GTA_ALL_MAP_TYPES | ArchetypeFlags::GTA_OBJECT_TYPE; // 3/28/13 - cthomas - We want to skip vehicles, so don't include ArchetypeFlags::GTA_VEHICLE_TYPE! //! DMKH. but we do want to prevent sticky aiming to targets behind vehicles. if(ms_Tunables.GetUseFirstPersonStickyAim() || GetAreCNCFreeAimImprovementsEnabled(m_pPed)) { flags |= ArchetypeFlags::GTA_VEHICLE_TYPE; } pedCapsuleDesc.SetIncludeFlags(flags); pedCapsuleDesc.SetExcludeEntity(m_pPed, WorldProbe::EIEO_DONT_ADD_VEHICLE_OCCUPANTS); pedCapsuleDesc.SetIsDirected(true); WorldProbe::GetShapeTestManager()->SubmitTest(pedCapsuleDesc, blockingMode); #if DEBUG_DRAW if(ms_Tunables.m_DisplayAimAssistTest) { float fTestRadius = m_pPed->GetIsInVehicle() ? ms_Tunables.GetAimAssistCapsuleRadiusInVehicle() : ms_Tunables.GetAimAssistCapsuleRadius(); CWeaponDebug::ms_debugStore.AddCapsule(RCC_VEC3V(vStart), RCC_VEC3V(vEnd), fTestRadius, Color_orange, 1000, ATSTRINGHASH("FreeAimAssistCapsule", 0xAAE041A3), false); } #endif // DEBUG_DRAW } } bool CPlayerPedTargeting::GetIsAiming() const { return GetLockOnTarget() || m_pPed->GetPlayerInfo()->GetPlayerDataFreeAiming() || m_pPed->GetPlayerInfo()->GetPlayerResetFlags().IsFlagSet(CPlayerResetFlags::PRF_ASSISTED_AIMING_ON); } bool CPlayerPedTargeting::IsPushingStick(const float fThreshHold) const { const CControl* pControl = m_pPed->GetControlFromPlayer(); if (pControl) { Vector2 vecStickInput; vecStickInput.x = pControl->GetPedAimWeaponLeftRight().GetUnboundNorm(); vecStickInput.y = pControl->GetPedAimWeaponUpDown().GetUnboundNorm(); if (Abs(vecStickInput.x) > fThreshHold|| Abs(vecStickInput.y) > fThreshHold) { return true; } } return false; } bool CPlayerPedTargeting::IsFineAimWithinBreakLimits() const { Vector3 vFineAim; GetFineAimingOffset(vFineAim); if (Abs(vFineAim.x) < ms_Tunables.GetSoftLockFineAimBreakXYValue() && Abs(vFineAim.y) < ms_Tunables.GetSoftLockFineAimBreakXYValue() && Abs(vFineAim.z) < ms_Tunables.GetSoftLockFineAimBreakZValue()) { return true; } return false; } bool CPlayerPedTargeting::IsFineAimWithinBreakXYLimits() const { Vector3 vFineAim; GetFineAimingOffset(vFineAim); if (Abs(vFineAim.x) < ms_Tunables.GetSoftLockFineAimBreakXYValue() && Abs(vFineAim.y) < ms_Tunables.GetSoftLockFineAimBreakXYValue()) { return true; } return false; } bool CPlayerPedTargeting::IsFineAimWithinAbsoluteXYLimits(float fLimit) const { Vector3 vFineAim; GetFineAimingOffset(vFineAim); if (Abs(vFineAim.x) < fLimit && Abs(vFineAim.y) < fLimit) { return true; } return false; } bool CPlayerPedTargeting::HasDeadLockonTarget() const { return m_pLockOnTarget && m_pLockOnTarget->GetIsTypePed() && static_cast(m_pLockOnTarget.Get())->IsDead(); } bool CPlayerPedTargeting::GetIsPedValidForFreeAimAssist(const CPed* pTargetPed) const { pedFatalAssertf(m_pPed, "m_pPed is NULL"); pedFatalAssertf(pTargetPed, "pTargetPed is NULL"); static bank_bool IGNORE_DEAD_OR_DYING = false; if(pTargetPed->GetIsDeadOrDying() && !IGNORE_DEAD_OR_DYING) { // Ped is dead/dying = ignore return false; } static bank_bool IGNORE_THREATENED_BY = true; if(!pTargetPed->GetPedIntelligence()->IsThreatenedBy(*m_pPed) && !IGNORE_THREATENED_BY) { // Ped is not a threat return false; } return true; } float CPlayerPedTargeting::GetScoreForFreeAimAssistIntersection(const Vector3& vStart, const Vector3& vEnd, const Vector3& vHitPos) const { // Construct a line representing the aiming vector fwGeom::fwLine line(vStart, vEnd); // Get the closest point to the intersection along the aiming vector Vector3 vClosestPoint; line.FindClosestPointOnLine(vHitPos, vClosestPoint); // Do a distance check to the aiming vector float fDistSq = vHitPos.Dist2(vClosestPoint); return fDistSq; } bool CPlayerPedTargeting::GetMoreAccurateFreeAimAssistTargetPos(const Vector3& vStart, const Vector3& vEnd, const Vector3& vHitPos, const CEntity* pHitEntity, Vector3& vNewHitPos) const { pedFatalAssertf(m_pPed, "m_pPed is NULL"); // // Attempt to find a more accurate target position // Construct a horizontal probe from the nearest point on the aiming vector in the direction of the hit position // fwGeom::fwLine line(vStart, vEnd); Vector3 vClosestPoint; // SNAPSHOT special designer test #if __BANK if( ms_bEnableSnapShotBulletBend && pHitEntity && pHitEntity->GetIsTypePed() ) { const CPlayerSpecialAbility* pSpecialAbility = m_pPed->GetSpecialAbility(); if( pSpecialAbility && pSpecialAbility->GetType() == SAT_SNAPSHOT && pSpecialAbility->IsActive() ) { // Determine if the target head position is within snapshot radius Vector3 vHeadPos = ((CPed*)pHitEntity)->GetBonePositionCached(BONETAG_HEAD); float fLineDist = line.FindClosestPointOnLine(vHeadPos, vClosestPoint); float fHeadDist = (vHeadPos - vClosestPoint).Mag(); if( fHeadDist < Lerp( fLineDist, ms_fSnapShotRadiusMin, ms_fSnapShotRadiusMax ) ) { vNewHitPos = vHeadPos; return true; } } } #endif float fT = line.FindClosestPointOnLine(vHitPos, vClosestPoint); float fRadiusAtDistance = GetFreeAimRadiusAtADistance( fT ); if( fRadiusAtDistance > 0.0f ) { // Get the horizontal points Vector3 vAimDir(vEnd - vStart); vAimDir.NormalizeFast(); Vector3 vRight; vRight.Cross(vAimDir, ZAXIS); vRight.NormalizeFast(); Vector3 vToIntersectionPos(vHitPos - vClosestPoint); vToIntersectionPos.NormalizeFast(); Vector3 vAimAtPos; TUNE_GROUP_BOOL(PLAYER_TARGETING, USE_TO_INTERSECTION_VECTOR, true); if (USE_TO_INTERSECTION_VECTOR) { vAimAtPos = vClosestPoint + (vToIntersectionPos * fRadiusAtDistance); } else { // Perform a dot product to work out the direction the probe should go float fDot = vRight.Dot(vToIntersectionPos); if(fDot > 0.0f) { vAimAtPos = vClosestPoint + (vRight * fRadiusAtDistance); } else { vAimAtPos = vClosestPoint - (vRight * fRadiusAtDistance); } } // Line probe to determine if we should change the intended hit position static dev_float CAPSULE_RADIUS = 0.05f; WorldProbe::CShapeTestCapsuleDesc pedCapsuleDesc; WorldProbe::CShapeTestHitPoint result; WorldProbe::CShapeTestResults probeResult(result); pedCapsuleDesc.SetResultsStructure( &probeResult ); pedCapsuleDesc.SetCapsule( vClosestPoint, vAimAtPos, CAPSULE_RADIUS ); pedCapsuleDesc.SetIsDirected( true ); pedCapsuleDesc.SetIncludeFlags( ArchetypeFlags::GTA_RAGDOLL_TYPE ); pedCapsuleDesc.SetIncludeEntity( pHitEntity ); pedCapsuleDesc.SetDomainForTest( WorldProbe::TEST_AGAINST_INDIVIDUAL_OBJECTS ); pedCapsuleDesc.SetDoInitialSphereCheck( true ); const bool bHitRagdoll = WorldProbe::GetShapeTestManager()->SubmitTest( pedCapsuleDesc ); #if __BANK if(CPedTargetEvaluator::ms_Tunables.m_DebugTargetting) { //CWeaponDebug::ms_debugStore.AddCapsule(RCC_VEC3V(vClosestPoint), RCC_VEC3V(vAimAtPos), CAPSULE_RADIUS, bHitRagdoll ? Color_cyan : Color_red, 1000, ATSTRINGHASH("PLAYER_PED_TARGETING_HORZ_LINE", 0x6A664A90), false); } #endif // __BANK if(bHitRagdoll) { vNewHitPos = probeResult[0].GetHitPosition(); return true; } } // Getting here means we are outside the free aim assist influence and we should fire where we are pointing vNewHitPos = vEnd; return false; } // Returns the bullet bending radius given a distance float CPlayerPedTargeting::GetFreeAimRadiusAtADistance( float fDistToTarget ) const { bool bZoomed = IsCameraInAccurateMode(); weaponAssert(m_pPed->GetWeaponManager()); const CWeapon* pWeapon = m_pPed->GetWeaponManager()->GetEquippedWeapon(); if( pWeapon && pWeapon->GetWeaponInfo()->GetIsGunOrCanBeFiredLikeGun() ) { float fNearRadius = 0.0f; float fFarRadius = 0.0f; GetBulletBendingData(m_pPed, pWeapon, bZoomed, fNearRadius, fFarRadius); return fNearRadius + fDistToTarget * (fFarRadius - fNearRadius); } return 0.0f; } const CWeaponInfo* CPlayerPedTargeting::GetWeaponInfo() const { pedFatalAssertf(m_pPed, "m_pPed is NULL"); return GetWeaponInfo(m_pPed); } const CWeaponInfo* CPlayerPedTargeting::GetWeaponInfo(CPed *pPed) { pedFatalAssertf(pPed, "pPed is NULL"); CPedWeaponManager *pWeapMgr = pPed->GetWeaponManager(); if (pWeapMgr) { if(pWeapMgr->GetEquippedVehicleWeapon()) { return pWeapMgr->GetEquippedVehicleWeapon()->GetWeaponInfo(); } else { if(pWeapMgr->GetEquippedWeaponInfo() == pWeapMgr->GetSelectedWeaponInfo()) { return pWeapMgr->GetEquippedWeaponInfo(); } } } return NULL; } atHashWithStringNotFinal CPlayerPedTargeting::GetTargetSequenceGroup() const { const CWeaponInfo* pInfo = GetWeaponInfo(); if (pInfo) { return pInfo->GetTargetSequenceGroup(); } return (u32)0; } CCurveSet* CPlayerPedTargeting::GetCurveSet(s32 iCurveType) { bool bFirstPerson = FPS_MODE_SUPPORTED_ONLY(CGameWorld::FindLocalPlayer() ? CGameWorld::FindLocalPlayer()->IsFirstPersonShooterModeEnabledForPlayer(false) :) false; if (aiVerifyf(iCurveType >= 0 && iCurveType < CTargettingDifficultyInfo::CS_Max, "Invalid curve type %i", iCurveType)) { switch(CPedTargetEvaluator::GetTargetMode(CGameWorld::FindLocalPlayer())) { case(CPedTargetEvaluator::TARGETING_OPTION_GTA_TRADITIONAL): return bFirstPerson ? &ms_Tunables.m_FirstPersonEasyTargettingDifficultyInfo.m_CurveSets[iCurveType] : &ms_Tunables.m_EasyTargettingDifficultyInfo.m_CurveSets[iCurveType]; case(CPedTargetEvaluator::TARGETING_OPTION_ASSISTED_AIM): return bFirstPerson ? &ms_Tunables.m_FirstPersonNormalTargettingDifficultyInfo.m_CurveSets[iCurveType] : &ms_Tunables.m_NormalTargettingDifficultyInfo.m_CurveSets[iCurveType]; case(CPedTargetEvaluator::TARGETING_OPTION_ASSISTED_FREEAIM): return bFirstPerson ? &ms_Tunables.m_FirstPersonHardTargettingDifficultyInfo.m_CurveSets[iCurveType] : &ms_Tunables.m_HardTargettingDifficultyInfo.m_CurveSets[iCurveType]; case(CPedTargetEvaluator::TARGETING_OPTION_FREEAIM): return bFirstPerson ? &ms_Tunables.m_FirstPersonExpertTargettingDifficultyInfo.m_CurveSets[iCurveType] : &ms_Tunables.m_ExpertTargettingDifficultyInfo.m_CurveSets[iCurveType]; } } Assert(0); return &ms_Tunables.m_EasyTargettingDifficultyInfo.m_CurveSets[iCurveType]; } CCurveSet* CPlayerPedTargeting::GetAimDistanceScaleCurve() { bool bFirstPerson = FPS_MODE_SUPPORTED_ONLY(CGameWorld::FindLocalPlayer() ? CGameWorld::FindLocalPlayer()->IsFirstPersonShooterModeEnabledForPlayer(false) :) false; switch(CPedTargetEvaluator::GetTargetMode(CGameWorld::FindLocalPlayer())) { case(CPedTargetEvaluator::TARGETING_OPTION_GTA_TRADITIONAL): return bFirstPerson ? &ms_Tunables.m_FirstPersonEasyTargettingDifficultyInfo.m_AimAssistDistanceCurve : &ms_Tunables.m_EasyTargettingDifficultyInfo.m_AimAssistDistanceCurve; case(CPedTargetEvaluator::TARGETING_OPTION_ASSISTED_AIM): return bFirstPerson ? &ms_Tunables.m_FirstPersonNormalTargettingDifficultyInfo.m_AimAssistDistanceCurve : &ms_Tunables.m_NormalTargettingDifficultyInfo.m_AimAssistDistanceCurve; case(CPedTargetEvaluator::TARGETING_OPTION_ASSISTED_FREEAIM): return bFirstPerson ? &ms_Tunables.m_FirstPersonHardTargettingDifficultyInfo.m_AimAssistDistanceCurve : &ms_Tunables.m_HardTargettingDifficultyInfo.m_AimAssistDistanceCurve; case(CPedTargetEvaluator::TARGETING_OPTION_FREEAIM): return bFirstPerson ? &ms_Tunables.m_FirstPersonExpertTargettingDifficultyInfo.m_AimAssistDistanceCurve : &ms_Tunables.m_ExpertTargettingDifficultyInfo.m_AimAssistDistanceCurve; } Assert(0); return &ms_Tunables.m_EasyTargettingDifficultyInfo.m_AimAssistDistanceCurve; } bool CPlayerPedTargeting::ShouldUsePullGunSpeech(const CWeaponInfo* pWeaponInfo) { if(pWeaponInfo->GetDamageType() != DAMAGE_TYPE_BULLET && pWeaponInfo->GetDamageType() != DAMAGE_TYPE_BULLET_RUBBER) return false; CEntity* pTargetEntity = m_pLockOnTarget.Get(); if(pTargetEntity == NULL && m_pFreeAimTarget != NULL) { pTargetEntity = m_pFreeAimTarget.Get(); } if(!pTargetEntity || ! m_pPed || pTargetEntity->GetType() != ENTITY_TYPE_PED || static_cast(pTargetEntity)->HasAnimalAudioParams() || !static_cast(pTargetEntity)->GetTaskData().GetIsFlagSet(CTaskFlags::CanBeTalkedTo) || static_cast(pTargetEntity)->GetIsDeadOrDying()) { return false; } //Take logic from determining lock-on target, and swap the target and the player, to make sure the target is looking at us Vector3 vTargetToPlayer = VEC3V_TO_VECTOR3(m_pPed->GetTransform().GetPosition() - pTargetEntity->GetTransform().GetPosition()); if(vTargetToPlayer.Mag2() > 250.0f) return false; vTargetToPlayer.z = 0.0f; vTargetToPlayer.Normalize(); Vector3 targetHeading = VEC3V_TO_VECTOR3(pTargetEntity->GetTransform().GetForward()); targetHeading.z = 0.0f; targetHeading.Normalize(); const float fDot = targetHeading.Dot(vTargetToPlayer); TUNE_GROUP_FLOAT(ONE_H_AIM, dotTolerance2, 0.85f, 0.0f, 1.0f, 0.01f); return fDot > dotTolerance2; } // Get weapon lock on range. float CPlayerPedTargeting::GetWeaponLockOnRange(const CWeaponInfo* pWeaponInfo, CEntity *pEntity) const { if(m_fLockOnRangeOverride >= 0.0f) { return m_fLockOnRangeOverride; } if(pWeaponInfo) { if(pWeaponInfo->GetIsUnarmed() && m_pPed->GetIsInCover()) { return ms_Tunables.m_UnarmedInCoverTargetingDistance; } float fRejectionModifier = 1.0f; if(CPedTargetEvaluator::CanBeTargetedInRejectionDistance(m_pPed, pWeaponInfo, pEntity)) { fRejectionModifier = ms_Tunables.GetLockOnDistanceRejectionModifier(); } return pWeaponInfo->GetLockOnRange() * fRejectionModifier; } return 0.0f; } bool CPlayerPedTargeting::IsMeleeTargetingUsingNormalLock() const { return m_pPed->GetIsInCover(); } bool CPlayerPedTargeting::CanUseNoReticuleAimAssist() const { bool bRunAndGun = m_pPed->GetPlayerInfo()->IsRunAndGunning(); #if FPS_MODE_SUPPORTED if( m_pPed->IsFirstPersonShooterModeEnabledForPlayer(false) ) { return false; } #endif if(!m_bNoReticuleAimAssist && m_pPed->GetPedConfigFlag(CPED_CONFIG_FLAG_IsAimingGun)) { return false; } const camGameplayDirector& gameplayDirector = camInterface::GetGameplayDirector(); if(!gameplayDirector.ShouldDisplayReticule() && !m_bBlockNoReticuleAimAssist && bRunAndGun && (m_pLockOnTarget == NULL) && (GetWeaponInfo() && GetWeaponInfo()->GetIsGun()) && !NetworkInterface::IsGameInProgress()) { return true; } return false; } bool CPlayerPedTargeting::IsCameraInAccurateMode() { camThirdPersonAimCamera* pAimCam = camInterface::GetGameplayDirector().GetThirdPersonAimCamera(); if(pAimCam) { return pAimCam->IsInAccurateMode(); } #if FPS_MODE_SUPPORTED else { camFirstPersonShooterCamera* pFirstPersonShooterCam = camInterface::GetGameplayDirector().GetFirstPersonShooterCamera(); if(pFirstPersonShooterCam) { return pFirstPersonShooterCam->IsScopeAiming(); } } #endif return false; } float CPlayerPedTargeting::ComputeExtraZoomFactorForAccurateMode(const CWeaponInfo& weaponInfo) { float extraZoomFactor = 1.0f; if(CGameWorld::FindLocalPlayer()) { //Check if this weapon has a scope component in the attach ped's inventory. const CPed* attachPed = CGameWorld::FindLocalPlayer(); const CPedInventory* inventory = attachPed->GetInventory(); if(inventory) { const CWeaponItem* weaponItem = inventory->GetWeapon(weaponInfo.GetHash()); if(weaponItem) { if(weaponItem->GetComponents()) { const CWeaponItem::Components& components = *weaponItem->GetComponents(); for(s32 i=0; iGetClassId() == WCT_Scope)) { extraZoomFactor = static_cast(componentInfo)-> GetExtraZoomFactorForAccurateMode(); break; } } } } } } return extraZoomFactor; } void CPlayerPedTargeting::GetBulletBendingData(const CPed *pPed, const CWeapon* pWeapon, bool bZoomed, float &fNear, float &fFar) { const bool bUsingCNCFreeAimImprovements = GetAreCNCFreeAimImprovementsEnabled(pPed, true); // CNC Free Aim: use vehicle velocity bullet bending modifiers. if(pPed->IsInFirstPersonVehicleCamera() || (bUsingCNCFreeAimImprovements && pPed->GetIsInVehicle())) { float fSpeed = 0.0f; CVehicle *pVehicle = pPed->GetVehiclePedInside(); if(pVehicle) { fSpeed = pVehicle->GetVelocity().Mag(); } float fSpeedScale = Min( fSpeed / ms_Tunables.GetInVehicleBulletBendingMaxVelocity(), 1.0f); fNear = Lerp(fSpeedScale, ms_Tunables.GetInVehicleBulletBendingNearMinVelocity(), ms_Tunables.GetInVehicleBulletBendingNearMaxVelocity()); fFar = Lerp(fSpeedScale, ms_Tunables.GetInVehicleBulletBendingFarMinVelocity(), ms_Tunables.GetInVehicleBulletBendingFarMaxVelocity()); } else if(pPed->IsFirstPersonShooterModeEnabledForPlayer(false)) { fNear = pWeapon->GetWeaponInfo()->GetFirstPersonBulletBendingNearRadius() * ms_Tunables.GetBulletBendingNearMultiplier(); fFar = bZoomed ? pWeapon->GetWeaponInfo()->GetFirstPersonBulletBendingZoomedRadius() * ms_Tunables.GetBulletBendingZoomMultiplier() : pWeapon->GetWeaponInfo()->GetFirstPersonBulletBendingFarRadius() * ms_Tunables.GetBulletBendingFarMultiplier(); } else { fNear = pWeapon->GetWeaponInfo()->GetBulletBendingNearRadius() * ms_Tunables.GetBulletBendingNearMultiplier(); fFar = bZoomed ? pWeapon->GetWeaponInfo()->GetBulletBendingZoomedRadius() * ms_Tunables.GetBulletBendingZoomMultiplier() : pWeapon->GetWeaponInfo()->GetBulletBendingFarRadius() * ms_Tunables.GetBulletBendingFarMultiplier(); } // CNC Free Aim: apply extra bullet bending modifier. // (Don't apply modifier if player/target are not moving fast enough) if (pPed && GetAreCNCFreeAimImprovementsEnabled(pPed)) { const CPlayerInfo* pPlayerInfo = pPed->GetPlayerInfo(); if (pPlayerInfo) { const CPlayerPedTargeting& rTargeting = pPlayerInfo->GetTargeting(); const CEntity* pFreeAimAssistTargetEntity = rTargeting.GetFreeAimAssistTarget(); if (pFreeAimAssistTargetEntity && pFreeAimAssistTargetEntity->GetIsTypePed()) { TUNE_GROUP_FLOAT(PLAYER_TARGETING, fCNCFreeAimBulletBendingModifierMinVelocity, 0.075f, 0.0f, 50.0f, 0.01f); const CPed* pFreeAimAssistTargetPed = static_cast(pFreeAimAssistTargetEntity); float fTargetVelocity = 0.0f; if (pFreeAimAssistTargetPed->GetIsInVehicle()) { const CVehicle* pFreeAimAssistTargetVehicle = pFreeAimAssistTargetPed->GetVehiclePedInside(); fTargetVelocity = pFreeAimAssistTargetVehicle->GetVelocity().Mag(); } else { fTargetVelocity = Mag(Subtract(VECTOR3_TO_VEC3V(pFreeAimAssistTargetPed->GetVelocity()), pFreeAimAssistTargetPed->GetGroundVelocity())).Getf(); } float fPlayerVelocity = 0.0f; if (pPed->GetIsInVehicle()) { const CVehicle* pVehicle = pPed->GetVehiclePedInside(); fPlayerVelocity = pVehicle->GetVelocity().Mag(); } else { fPlayerVelocity = Mag(Subtract(VECTOR3_TO_VEC3V(pPed->GetVelocity()), pPed->GetGroundVelocity())).Getf(); } const float fRelativeVelocity = abs(fTargetVelocity - fPlayerVelocity); if (fTargetVelocity > fCNCFreeAimBulletBendingModifierMinVelocity || fPlayerVelocity > fCNCFreeAimBulletBendingModifierMinVelocity || fRelativeVelocity > fCNCFreeAimBulletBendingModifierMinVelocity) { // Using a lower value for in vehicles as bullet bending radius is already larger and scaled by velocity in the above code. TUNE_GROUP_FLOAT(PLAYER_TARGETING, fCNCFreeAimExtraBulletBendingModifierOnFoot, 2.2f, 1.0f, 10.0f, 0.01f); TUNE_GROUP_FLOAT(PLAYER_TARGETING, fCNCFreeAimExtraBulletBendingModifierInVehicle, 1.65f, 1.0f, 10.0f, 0.01f); float fCNCFreeAimExtraBulletBendingModifier = pPed->GetIsInVehicle() ? fCNCFreeAimExtraBulletBendingModifierInVehicle : fCNCFreeAimExtraBulletBendingModifierOnFoot; // Cop/Crook-specific modifiers. if (pPlayerInfo->GetArcadeInformation().GetTeam() == eArcadeTeam::AT_CNC_COP) { TUNE_GROUP_FLOAT(PLAYER_TARGETING, fCNCFreeAimExtraBulletBendingModifierOnFoot_COP, 2.2f, 1.0f, 10.0f, 0.01f); TUNE_GROUP_FLOAT(PLAYER_TARGETING, fCNCFreeAimExtraBulletBendingModifierInVehicle_COP, 1.65f, 1.0f, 10.0f, 0.01f); fCNCFreeAimExtraBulletBendingModifier = pPed->GetIsInVehicle() ? fCNCFreeAimExtraBulletBendingModifierInVehicle_COP : fCNCFreeAimExtraBulletBendingModifierOnFoot_COP; } else if (pPlayerInfo->GetArcadeInformation().GetTeam() == eArcadeTeam::AT_CNC_CROOK) { TUNE_GROUP_FLOAT(PLAYER_TARGETING, fCNCFreeAimExtraBulletBendingModifierOnFoot_CROOK, 1.925f, 1.0f, 10.0f, 0.01f); TUNE_GROUP_FLOAT(PLAYER_TARGETING, fCNCFreeAimExtraBulletBendingModifierInVehicle_CROOK, 1.375f, 1.0f, 10.0f, 0.01f); fCNCFreeAimExtraBulletBendingModifier = pPed->GetIsInVehicle() ? fCNCFreeAimExtraBulletBendingModifierInVehicle_CROOK : fCNCFreeAimExtraBulletBendingModifierOnFoot_CROOK; } fNear *= fCNCFreeAimExtraBulletBendingModifier; fFar *= fCNCFreeAimExtraBulletBendingModifier; } } } } }