Files
GTASource/game/camera/helpers/ControlHelper.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

2084 lines
77 KiB
C++

//
// camera/helpers/ControlHelper.cpp
//
// Copyright (C) 1999-2011 Rockstar Games. All Rights Reserved.
//
#include "camera/helpers/ControlHelper.h"
#include "math/vecmath.h"
#include "fwsys/timer.h"
#include "input/input.h"
#include "system/memops.h"
#include "camera/CamInterface.h"
#include "camera/cinematic/CinematicDirector.h"
#include "camera/gameplay/GameplayDirector.h"
#include "camera/helpers/Envelope.h"
#include "camera/system/CameraFactory.h"
#include "frontend/PauseMenu.h"
#include "frontend/ProfileSettings.h"
#include "modelinfo/VehicleModelInfoMods.h"
#include "modelinfo/VehicleModelInfoVariation.h"
#include "system/control.h"
#include "frontend/MobilePhone.h"
#include "control/replay/replay.h"
// CS: Includes for Haxx targeting code
#include "peds/Ped.h"
#include "scene/world/GameWorld.h"
CAMERA_OPTIMISATIONS()
INSTANTIATE_RTTI_CLASS(camControlHelper,0xD026E9A8)
REGISTER_TUNE_GROUP_INT(LOOK_BEHIND_HELD_TIME, 150);
#define CONTROLLER_ORIENTATION_ADD_TO_STICK_INPUT (RSG_ORBIS)
static const s32 g_DefaultViewMode = camControlHelperMetadataViewMode::THIRD_PERSON_MEDIUM;
static const s32 g_OriginalMaxControllerAimSensitivity = 10; //It is apparently not possible to query this limit from the UI Code at present.
static const s32 g_RevisedMaxControllerAimSensitivity = 14; //We are extending the upper limit of aim sensitivity post-launch.
static const s32 g_DefaultControllerAimSensitivity = 5; //NOTE: This is the midpoint of the original aim sensitivity range.
static const s32 g_DefaultControllerDeadZone = 14; //NOTE: This is the max of the original range.
static const s32 g_OriginalMaxControllerDeadZone = 14;
static const s32 g_DefaultControllerAcceleration = 7; //NOTE: This is the midpoint of original range.
static const s32 g_OriginalMaxControllerAcceleration = 14; //NOTE: This is the max of the original range.
static const float g_MinFovForInsideMpVehicle = 35.0f;
const float MOUSE_POWER_EXPONENT = 1.3f;
EXTERN_PARSER_ENUM(camControlHelperMetadataViewMode__eViewModeContext);
EXTERN_PARSER_ENUM(camControlHelperMetadataViewMode__eViewMode);
Vector2 camControlHelper::ms_OverriddenZoomFactorLimits;
s32* camControlHelper::ms_ContextViewModes = NULL;
float camControlHelper::ms_ZoomFactor = 1.0f;
bool camControlHelper::ms_ShouldOverrideZoomFactorLimits = false;
bool camControlHelper::ms_MaintainCurrentViewMode = false;
s32 camControlHelper::ms_LastViewModeContext = -1;
s32 camControlHelper::ms_LastSetViewMode = -1;
s32 camControlHelper::ms_ContextToForceFirstPersonCamera= -1;
s32 camControlHelper::ms_ContextToForceThirdPersonCamera= -1;
s32 camControlHelper::ms_ThirdPersonViewModeToForce = -1;
bool camControlHelper::ms_FirstPersonCancelledForCinematicCamera = false;
#if __BANK
float camControlHelper::ms_LookAroundSpeedScalar = 1.0f;
bool camControlHelper::ms_UseOverriddenLookAroundHelper = false;
camControlHelperMetadataLookAround camControlHelper::ms_OverriddenLookAroundHelper;
s32 camControlHelper::ms_DebugAimSensitivitySetting = -1;
s32 camControlHelper::ms_DebugLookAroundSensitivitySetting = -1;
s32 camControlHelper::ms_DebugRequestedContextIndex = -1;
s32 camControlHelper::ms_DebugRequestedViewModeForContext = -1;
bool camControlHelper::ms_DebugWasViewModeForContextSetThisUpdate = false;
#endif // __BANK
#if RSG_ORBIS
static BankFloat c_fMotionDeadzone = 1.0f; // degrees;
static BankFloat c_fMotionVerticalScale = 0.00f;
static BankFloat c_fMotionHorizontalScale = 0.00f;
#endif
NOSTRIP_PC_PARAM(mouse_sensitivity_override, "Mouse sensitivity override");
void camControlHelper::InitClass()
{
ms_OverriddenZoomFactorLimits.Zero();
if(camControlHelperMetadataViewMode::eViewModeContext_NUM_ENUMS > 0)
{
ms_ContextViewModes = rage_new s32[camControlHelperMetadataViewMode::eViewModeContext_NUM_ENUMS];
if(ms_ContextViewModes)
{
//TODO: Store a default view mode for each context in the camera metadata.
for(s32 i=0; i<camControlHelperMetadataViewMode::eViewModeContext_NUM_ENUMS; i++)
{
ms_ContextViewModes[i] = g_DefaultViewMode;
}
ms_ContextViewModes[camControlHelperMetadataViewMode::ON_BIKE] = camControlHelperMetadataViewMode::THIRD_PERSON_NEAR;
ms_ContextViewModes[camControlHelperMetadataViewMode::IN_AIRCRAFT] = camControlHelperMetadataViewMode::THIRD_PERSON_FAR;
ms_ContextViewModes[camControlHelperMetadataViewMode::IN_SUBMARINE] = camControlHelperMetadataViewMode::THIRD_PERSON_NEAR;
}
}
}
void camControlHelper::ShutdownClass()
{
if(ms_ContextViewModes)
{
delete[] ms_ContextViewModes;
ms_ContextViewModes = NULL;
}
}
s32 camControlHelper::GetViewModeForContextInternal(s32 contextIndex)
{
const s32* viewModeForContext = GetViewModeForContextWithValidation(contextIndex);
s32 viewMode = viewModeForContext ? *viewModeForContext : g_DefaultViewMode;
return viewMode;
}
s32 camControlHelper::GetViewModeForContext(s32 contextIndex)
{
s32 viewMode = GetViewModeForContextInternal(contextIndex);
#if FPS_MODE_SUPPORTED
if(ms_ContextToForceFirstPersonCamera != -1 && viewMode != camControlHelperMetadataViewMode::FIRST_PERSON)
{
viewMode = camControlHelperMetadataViewMode::FIRST_PERSON;
}
if(ms_ContextToForceThirdPersonCamera != -1 && viewMode == camControlHelperMetadataViewMode::FIRST_PERSON)
{
viewMode = ms_ThirdPersonViewModeToForce;
}
#endif
return viewMode;
}
bool camControlHelper::SetViewModeForContext(s32 contextIndex, s32 viewMode)
{
if(!cameraVerifyf((viewMode >= 0) && (viewMode <= camControlHelperMetadataViewMode::eViewMode_MAX_VALUE), "Invalid camera view mode (%d)", viewMode))
{
return false;
}
#if __BANK
ms_DebugWasViewModeForContextSetThisUpdate = true;
ms_DebugRequestedContextIndex = contextIndex;
ms_DebugRequestedViewModeForContext = viewMode;
cameraDebugf2("View-mode update: SetViewModeForContext %s = %s", (contextIndex >= 0 && contextIndex < camControlHelperMetadataViewMode::eViewModeContext_NUM_ENUMS) ? PARSER_ENUM(camControlHelperMetadataViewMode__eViewModeContext).m_Names[contextIndex] : "UNKNOWN", PARSER_ENUM(camControlHelperMetadataViewMode__eViewMode).m_Names[viewMode]);
#endif // __BANK
s32* viewModeForContext = GetViewModeForContextWithValidation(contextIndex);
const bool isValid = (viewModeForContext != NULL);
if(isValid)
{
*viewModeForContext = viewMode;
#if FPS_MODE_SUPPORTED
if(ms_ContextToForceFirstPersonCamera != -1 && viewMode != camControlHelperMetadataViewMode::FIRST_PERSON)
{
ms_ContextToForceFirstPersonCamera = -1;
ms_FirstPersonCancelledForCinematicCamera = (viewMode == camControlHelperMetadataViewMode::CINEMATIC);
}
if(ms_ContextToForceThirdPersonCamera != -1)
{
if(viewMode == camControlHelperMetadataViewMode::FIRST_PERSON)
ms_ContextToForceThirdPersonCamera = -1;
else if(viewMode != camControlHelperMetadataViewMode::CINEMATIC)
ms_ThirdPersonViewModeToForce = viewMode;
}
#endif
}
return isValid;
}
s32* camControlHelper::GetViewModeForContextWithValidation(s32 contextIndex)
{
s32* viewMode = NULL;
if(cameraVerifyf(ms_ContextViewModes, "No camera view mode contexts have been defined"))
{
if(cameraVerifyf((contextIndex >= 0) && (contextIndex <= camControlHelperMetadataViewMode::eViewModeContext_MAX_VALUE),
"Invalid camera view mode context (%d)", contextIndex))
{
viewMode = ms_ContextViewModes + contextIndex;
}
}
return viewMode;
}
void camControlHelper::StoreContextViewModesInMap(atBinaryMap<atHashValue, atHashValue>& map)
{
for(s32 contextIndex=0; contextIndex<camControlHelperMetadataViewMode::eViewModeContext_NUM_ENUMS; contextIndex++)
{
const char* contextName = PARSER_ENUM(camControlHelperMetadataViewMode__eViewModeContext).m_Names[contextIndex];
if(contextName)
{
atHashValue contextNameHash(contextName);
if(cameraVerifyf(!map.Has(contextNameHash), "Found a duplicate name hash for a camera view mode context (name: %s, hash: %u)",
contextName, contextNameHash.GetHash()))
{
const s32 viewMode = GetViewModeForContextInternal(contextIndex);
const char* viewModeName = PARSER_ENUM(camControlHelperMetadataViewMode__eViewMode).m_Names[viewMode];
if(viewModeName)
{
atHashValue viewModeNameHash(viewModeName);
map.FastInsert(contextNameHash, viewModeNameHash);
}
}
}
}
map.FinishInsertion();
}
void camControlHelper::RestoreContextViewModesFromMap(const atBinaryMap<atHashValue, atHashValue>& map)
{
atBinaryMap<s32, atHashValue> viewModeMap;
for(s32 viewModeIndex=0; viewModeIndex<camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS; viewModeIndex++)
{
const char* viewModeName = PARSER_ENUM(camControlHelperMetadataViewMode__eViewMode).m_Names[viewModeIndex];
if(viewModeName)
{
atHashValue viewModeNameHash(viewModeName);
if(cameraVerifyf(!viewModeMap.Has(viewModeNameHash), "Found a duplicate name hash for a camera view mode (name: %s, hash: %u)",
viewModeName, viewModeNameHash.GetHash()))
{
viewModeMap.FastInsert(viewModeNameHash, viewModeIndex);
}
}
}
viewModeMap.FinishInsertion();
for(s32 contextIndex=0; contextIndex<camControlHelperMetadataViewMode::eViewModeContext_NUM_ENUMS; contextIndex++)
{
const char* contextName = PARSER_ENUM(camControlHelperMetadataViewMode__eViewModeContext).m_Names[contextIndex];
if(contextName)
{
atHashValue contextNameHash(contextName);
const atHashValue* viewModeNameHash = map.SafeGet(contextNameHash);
if(viewModeNameHash)
{
const s32* viewMode = viewModeMap.SafeGet(*viewModeNameHash);
if(viewMode)
{
ms_ContextViewModes[contextIndex] = *viewMode;
}
}
}
}
}
//static
u32 camControlHelper::GetBonnetCameraToggleTime()
{
return (u32)(LOOK_BEHIND_HELD_TIME);
}
void camControlHelper::OverrideZoomFactorLimits(float minZoomFactor, float maxZoomFactor)
{
const float minZoomFactorToApply = Max(minZoomFactor, 1.0f);
const float maxZoomFactorToApply = Max(maxZoomFactor, minZoomFactor);
ms_OverriddenZoomFactorLimits.Set(minZoomFactorToApply, maxZoomFactorToApply);
ms_ShouldOverrideZoomFactorLimits = true;
}
float camControlHelper::ComputeOffsetBasedOnAcceleratedInput(float input, float acceleration, float deceleration, float maxSpeed, float& speed,
float timeStep, bool shouldResetSpeedOnInputFlip)
{
//NOTE: Input has already been subjected to a dead-zone to filter out very small values.
if(shouldResetSpeedOnInputFlip && (input != 0.0f) && !SameSign(input, speed))
{
//Kill the speed instantly when the direction of movement reverses, in order to avoid lag due to deceleration.
speed = 0.0f;
}
float absSpeed = Abs(speed);
float absMaxSpeed = Abs(input * maxSpeed);
if(absMaxSpeed >= absSpeed)
{
//Accelerate.
float speedDelta = acceleration * timeStep;
speed += Sign(input) * speedDelta;
speed = Clamp(speed, -absMaxSpeed, absMaxSpeed); //Don't overshoot the max speed.
}
else
{
//Decelerate.
float speedDelta = deceleration * timeStep;
float maxAbsSpeedDelta = absSpeed - absMaxSpeed;
speedDelta = Clamp(speedDelta, -maxAbsSpeedDelta, maxAbsSpeedDelta); //Don't overshoot the max speed.
speed -= speedDelta * Sign(speed);
}
float offset = speed * timeStep;
return offset;
}
camControlHelper::camControlHelper(const camBaseObjectMetadata& metadata)
: camBaseObject(metadata)
, m_Metadata(static_cast<const camControlHelperMetadata&>(metadata))
, m_ViewModeBlendEnvelope(NULL)
, m_LookAroundInputEnvelope(NULL)
, m_ViewModeContext(-1)
, m_ViewMode(-1)
, m_LimitFirstPersonAimSensitivityThisUpdate(-1)
, m_UseLeftStickForLookInputThisUpdate(false)
, m_UseBothSticksForLookInputThisUpdate(false)
{
const camEnvelopeMetadata* envelopeMetadata =
camFactory::FindObjectMetadata<camEnvelopeMetadata>(m_Metadata.m_ViewModes.m_ViewModeBlendEnvelopeRef.GetHash());
if(envelopeMetadata)
{
m_ViewModeBlendEnvelope = camFactory::CreateObject<camEnvelope>(*envelopeMetadata);
cameraAssertf(m_ViewModeBlendEnvelope,
"A camera control helper (name: %s, hash: %u) failed to create a view mode blend envelope (name: %s, hash: %u)",
GetName(), GetNameHash(), SAFE_CSTRING(envelopeMetadata->m_Name.GetCStr()),
envelopeMetadata->m_Name.GetHash());
}
envelopeMetadata =
camFactory::FindObjectMetadata<camEnvelopeMetadata>(m_Metadata.m_LookAround.m_InputEnvelopeRef.GetHash());
if(envelopeMetadata)
{
m_LookAroundInputEnvelope = camFactory::CreateObject<camEnvelope>(*envelopeMetadata);
cameraAssertf(m_LookAroundInputEnvelope,
"A camera control helper (name: %s, hash: %u) failed to create a look-around input envelope (name: %s, hash: %u)",
GetName(), GetNameHash(), SAFE_CSTRING(envelopeMetadata->m_Name.GetCStr()),
envelopeMetadata->m_Name.GetHash());
}
sysMemSet(m_ViewModeSourceBlendLevels, 0, camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS * sizeof(float));
sysMemSet(m_ViewModeBlendLevels, 0, camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS * sizeof(float));
Reset();
}
camControlHelper::~camControlHelper()
{
if(m_ViewModeBlendEnvelope)
{
delete m_ViewModeBlendEnvelope;
}
if(m_LookAroundInputEnvelope)
{
delete m_LookAroundInputEnvelope;
}
}
const camControlHelperMetadataViewModeSettings* camControlHelper::GetViewModeSettings(s32 mode) const
{
const camControlHelperMetadataViewModeSettings* settings = NULL;
if (mode < 0 || mode >= camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS)
{
mode = m_ViewMode;
}
if(cameraVerifyf((mode >= 0),
"View mode settings were queried on a camera control helper (name: %s, hash: %u) prior to view mode init",
GetName(), GetNameHash()))
{
settings = &m_Metadata.m_ViewModes.m_Settings[mode];
}
return settings;
}
bool camControlHelper::IsViewModeBlending() const
{
const bool isBlending = m_ViewModeBlendEnvelope && m_ViewModeBlendEnvelope->IsActive();
return isBlending;
}
float camControlHelper::GetOverallViewModeBlendLevel() const
{
float blendLevel = 1.0f;
if(m_ViewModeBlendEnvelope && m_ViewModeBlendEnvelope->IsActive())
{
blendLevel = m_ViewModeBlendEnvelope->GetLevel();
}
return blendLevel;
}
float camControlHelper::ComputeMaxLookAroundOrientationSpeed() const
{
const float aimSensitivityScaling = GetAimSensitivityScaling();
//NOTE: We factor in the aim sensitivity, as this has a bearing on the effective look-around orientation speed.
float speed = Max(GetLookAroundMetadata().m_MaxHeadingSpeed * DtoR, GetLookAroundMetadata().m_MaxPitchSpeed * DtoR);
speed *= aimSensitivityScaling;
return speed;
}
bool camControlHelper::IsUsingZoomInput() const
{
return m_Metadata.m_Zoom.m_ShouldUseZoomInput;
}
float camControlHelper::GetZoomFov() const
{
const float fov = m_Metadata.m_Zoom.m_MaxFov / ms_ZoomFactor;
return fov;
}
bool camControlHelper::IsUsingLookBehindInput() const
{
const bool isUsingLookBehindInput = m_Metadata.m_ShouldUseLookBehindInput && m_IsLookBehindInputActive &&
!m_ShouldIgnoreLookBehindInputThisUpdate;
return isUsingLookBehindInput;
}
void camControlHelper::SetAccurateModeState(bool state)
{
if(m_Metadata.m_ShouldUseAccurateModeInput)
{
m_IsInAccurateMode = state;
}
}
void camControlHelper::CloneSpeeds(const camControlHelper& sourceControlHelper)
{
m_LookAroundSpeed = sourceControlHelper.GetLookAroundSpeed();
m_ZoomFactorSpeed = sourceControlHelper.GetZoomFactorSpeed();
}
void camControlHelper::Update(const CEntity& attachParent, bool isEarlyUpdate)
{
if(m_ViewModeContext < 0)
{
InitViewModeContext(attachParent);
}
if(m_ViewMode < 0)
{
InitViewMode();
}
CControl* control = camInterface::GetGameplayDirector().GetActiveControl();
if(control)
{
if(!m_SkipControlHelperThisUpdate)
{
UpdateLookBehindControl(*control, isEarlyUpdate);
UpdateViewModeControl(*control, attachParent);
UpdateLookAroundControl(*control);
UpdateZoomControl(*control);
UpdateAccurateModeControl(*control);
}
else
{
// Only reset the input, leave all other state as is. (so input acceleration is not modified)
m_LookAroundHeadingOffset = 0.0f;
m_LookAroundPitchOffset = 0.0f;
}
}
else
{
Reset(); //Clear the state.
}
ms_LastViewModeContext = m_ViewModeContext;
m_UseLeftStickForLookInputThisUpdate = false;
m_UseBothSticksForLookInputThisUpdate = false;
m_ShouldForceAimSensitivityThisUpdate = false;
m_ShouldForceDefaultLookAroundSensitivityThisUpdate = false;
m_SkipControlHelperThisUpdate = false;
m_LimitFirstPersonAimSensitivityThisUpdate = -1;
}
void camControlHelper::InitViewModeContext(const CEntity& attachParent)
{
m_ViewModeContext = m_Metadata.m_ViewModes.m_Context;
// For turrets, attachParent can be vehicle in the case of cinematic POV turret camera.
if( (camInterface::GetGameplayDirector().IsUsingVehicleTurret(true) && attachParent.GetIsTypePed())
FPS_MODE_SUPPORTED_ONLY(|| (camInterface::GetGameplayDirector().IsUsingVehicleTurret(false) && attachParent.GetIsTypeVehicle())) )
{
m_ViewModeContext = camControlHelperMetadataViewMode::IN_TURRET;
}
else if((m_ViewModeContext == camControlHelperMetadataViewMode::IN_VEHICLE) && attachParent.GetIsTypeVehicle())
{
//Assign a vehicle-class-specific view mode context where appropriate for our attach parent.
const CVehicle& attachVehicle = static_cast<const CVehicle&>(attachParent);
if(attachVehicle.InheritsFromBike() || attachVehicle.InheritsFromQuadBike() || attachVehicle.InheritsFromAmphibiousQuadBike())
{
m_ViewModeContext = camControlHelperMetadataViewMode::ON_BIKE;
}
else if(attachVehicle.InheritsFromBoat() || attachVehicle.GetIsJetSki() || attachVehicle.InheritsFromAmphibiousAutomobile() || attachVehicle.InheritsFromAmphibiousQuadBike())
{
m_ViewModeContext = camControlHelperMetadataViewMode::IN_BOAT;
}
else if(attachVehicle.InheritsFromHeli())
{
m_ViewModeContext = camControlHelperMetadataViewMode::IN_HELI;
}
else if(attachVehicle.GetIsAircraft() || attachVehicle.InheritsFromBlimp())
{
m_ViewModeContext = camControlHelperMetadataViewMode::IN_AIRCRAFT;
}
else if(attachVehicle.InheritsFromSubmarine())
{
m_ViewModeContext = camControlHelperMetadataViewMode::IN_SUBMARINE;
}
}
}
s32 camControlHelper::ComputeViewModeContextForVehicle(u32 ModelHashKey)
{
s32 viewModeContext = -1;
CBaseModelInfo* pModelInfo = CModelInfo::GetBaseModelInfoFromHashKey(ModelHashKey, NULL);
if(pModelInfo && pModelInfo->GetModelType()==MI_TYPE_VEHICLE)
{
CVehicleModelInfo* pVehicleModel = static_cast<CVehicleModelInfo*>(pModelInfo);
viewModeContext = camControlHelperMetadataViewMode::IN_VEHICLE;
if(pVehicleModel->GetIsBike() || pVehicleModel->GetIsQuadBike() || pVehicleModel->GetIsAmphibiousQuad())
{
viewModeContext = camControlHelperMetadataViewMode::ON_BIKE;
}
else if(pVehicleModel->GetIsBoat() || pVehicleModel->GetIsJetSki())
{
viewModeContext = camControlHelperMetadataViewMode::IN_BOAT;
}
else if(pVehicleModel->GetIsHeli() || pVehicleModel->GetVehicleType() == VEHICLE_TYPE_BLIMP)
{
viewModeContext = camControlHelperMetadataViewMode::IN_HELI;
}
else if(pVehicleModel->GetIsRotaryAircraft() || pVehicleModel->GetIsPlane() || pVehicleModel->GetVehicleType() == VEHICLE_TYPE_BLIMP)
{
viewModeContext = camControlHelperMetadataViewMode::IN_AIRCRAFT;
}
else if(pVehicleModel->GetIsSubmarine())
{
viewModeContext = camControlHelperMetadataViewMode::IN_SUBMARINE;
}
}
return viewModeContext;
}
s32 camControlHelper::ComputeViewModeContextForVehicle(const CVehicle& vehicle)
{
s32 viewModeContext = camControlHelperMetadataViewMode::IN_VEHICLE;
if(vehicle.InheritsFromBike() || vehicle.InheritsFromQuadBike() || vehicle.InheritsFromAmphibiousQuadBike())
{
viewModeContext = camControlHelperMetadataViewMode::ON_BIKE;
}
else if(vehicle.InheritsFromBoat() || vehicle.GetIsJetSki() || vehicle.InheritsFromAmphibiousAutomobile() || vehicle.InheritsFromAmphibiousQuadBike())
{
viewModeContext = camControlHelperMetadataViewMode::IN_BOAT;
}
else if(vehicle.InheritsFromHeli())
{
viewModeContext = camControlHelperMetadataViewMode::IN_HELI;
}
else if(vehicle.GetIsAircraft() || vehicle.InheritsFromBlimp())
{
viewModeContext = camControlHelperMetadataViewMode::IN_AIRCRAFT;
}
else if(vehicle.InheritsFromSubmarine())
{
viewModeContext = camControlHelperMetadataViewMode::IN_SUBMARINE;
}
return viewModeContext;
}
void camControlHelper::InitViewMode()
{
s32 viewMode = GetViewModeForContextInternal(m_ViewModeContext);
ValidateViewMode(viewMode);
m_ViewMode = viewMode;
m_ViewModeBlendLevels[m_ViewMode] = 1.0f;
#if FPS_MODE_SUPPORTED
// Only update the context to override first person camera for if the view mode context is changing.
if( ms_MaintainCurrentViewMode )
{
if( ms_LastViewModeContext != m_ViewModeContext )
{
bool bLastViewModeWasFirstPerson = ms_LastSetViewMode == (s32)camControlHelperMetadataViewMode::FIRST_PERSON ||
ms_ContextToForceFirstPersonCamera != -1 ||
(ms_FirstPersonCancelledForCinematicCamera && m_ViewMode != (s32)camControlHelperMetadataViewMode::CINEMATIC) ||
(ms_LastSetViewMode == -1 && camInterface::IsRenderingFirstPersonCamera());
if(m_ViewMode != camControlHelperMetadataViewMode::FIRST_PERSON && bLastViewModeWasFirstPerson)
{
const bool isModeValid = ComputeIsViewModeValid( (s32)camControlHelperMetadataViewMode::FIRST_PERSON );
if(isModeValid)
{
ms_ContextToForceFirstPersonCamera = m_ViewModeContext;
ms_ContextToForceThirdPersonCamera = -1;
m_ViewMode = camControlHelperMetadataViewMode::FIRST_PERSON;
}
}
else if(m_ViewMode == camControlHelperMetadataViewMode::FIRST_PERSON && !bLastViewModeWasFirstPerson)
{
s32 lastViewMode = ms_LastSetViewMode;
if(ms_LastSetViewMode < 0)
lastViewMode = (ms_LastViewModeContext != -1) ? GetViewModeForContextInternal(ms_LastViewModeContext) : g_DefaultViewMode;
if(lastViewMode == camControlHelperMetadataViewMode::FIRST_PERSON)
{
lastViewMode = g_DefaultViewMode;
}
if(!(lastViewMode == camControlHelperMetadataViewMode::CINEMATIC && (m_ViewModeContext >= camControlHelperMetadataViewMode::IN_VEHICLE && m_ViewModeContext <= camControlHelperMetadataViewMode::IN_TURRET)))
{
const bool isModeValid = ComputeIsViewModeValid( (s32)lastViewMode);
if(isModeValid)
{
ms_ContextToForceFirstPersonCamera = -1;
ms_ContextToForceThirdPersonCamera = m_ViewModeContext;
ms_ThirdPersonViewModeToForce = lastViewMode;
m_ViewMode = lastViewMode;
}
else
{
ms_ContextToForceFirstPersonCamera = -1;
ms_ContextToForceThirdPersonCamera = m_ViewModeContext;
ms_ThirdPersonViewModeToForce = g_DefaultViewMode;
m_ViewMode = g_DefaultViewMode;
}
}
}
// Even if we don't need to set it, treat as if we are overriding view mode so we know that a context is being overridden.
if(ms_ContextToForceFirstPersonCamera == -1 && ms_ContextToForceThirdPersonCamera != -1 && m_ViewMode == camControlHelperMetadataViewMode::FIRST_PERSON)
{
ms_ContextToForceFirstPersonCamera = m_ViewModeContext;
}
else if(ms_ContextToForceFirstPersonCamera == -1 && ms_ContextToForceThirdPersonCamera == -1 && m_ViewMode != camControlHelperMetadataViewMode::FIRST_PERSON)
{
ms_ContextToForceThirdPersonCamera = m_ViewModeContext;
ms_ThirdPersonViewModeToForce = m_ViewMode;
}
}
else
{
// Context did not change, ensure that the proper view mode is setup.
// Have to check if any context is overridden because of how animated camera in vehicles are handled.
if(ms_ContextToForceFirstPersonCamera != -1 && m_ViewMode != camControlHelperMetadataViewMode::FIRST_PERSON)
{
const bool isModeValid = ComputeIsViewModeValid( (s32)camControlHelperMetadataViewMode::FIRST_PERSON );
if(isModeValid)
{
m_ViewMode = camControlHelperMetadataViewMode::FIRST_PERSON;
ms_ContextToForceFirstPersonCamera = m_ViewModeContext;
}
else
{
ms_ContextToForceFirstPersonCamera = -1;
}
}
if(ms_ContextToForceThirdPersonCamera != -1 && m_ViewMode == camControlHelperMetadataViewMode::FIRST_PERSON)
{
s32 viewModeToUse = GetViewModeForContextInternal(m_ViewModeContext);
if(viewModeToUse == (s32)camControlHelperMetadataViewMode::FIRST_PERSON)
viewModeToUse = g_DefaultViewMode;
const bool isModeValid = ComputeIsViewModeValid( viewModeToUse );
if(isModeValid)
{
m_ViewMode = viewModeToUse;
ms_ContextToForceThirdPersonCamera = m_ViewModeContext;
}
else
{
ms_ContextToForceThirdPersonCamera = -1;
}
}
}
ms_FirstPersonCancelledForCinematicCamera &= (viewMode == camControlHelperMetadataViewMode::CINEMATIC);
}
else
{
ms_ContextToForceFirstPersonCamera = -1;
ms_ContextToForceThirdPersonCamera = -1;
ms_FirstPersonCancelledForCinematicCamera = false;
}
#endif
}
void camControlHelper::UpdateLookBehindControl(const CControl& control, bool isEarlyUpdate)
{
m_WasLookingBehind = m_IsLookingBehind;
const bool isUsingLookBehindInput = IsUsingLookBehindInput();
if(isUsingLookBehindInput)
{
const bool bIsInFlyingVehicle = (m_ViewModeContext == camControlHelperMetadataViewMode::IN_AIRCRAFT) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::IN_HELI);
const bool bIsInVehicle = (m_ViewModeContext == camControlHelperMetadataViewMode::IN_VEHICLE) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::ON_BIKE) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::IN_BOAT) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::IN_SUBMARINE) ||
bIsInFlyingVehicle;
bool bIsFlyingVehicleArmed = false;
if(bIsInFlyingVehicle)
{
const CPed* pPlayer = CGameWorld::FindLocalPlayer();
if(pPlayer && pPlayer->GetIsDrivingVehicle() && !pPlayer->GetIsDeadOrDying())
{
const CWeaponInfo *pCurrentWeaponInfo = pPlayer->GetEquippedWeaponInfo();
bIsFlyingVehicleArmed = pCurrentWeaponInfo && pCurrentWeaponInfo->GetIsVehicleWeapon();
}
}
const ioValue& lookBehindControl = bIsInVehicle ? control.GetVehicleLookBehind() : control.GetPedLookBehind();
const bool bIsOnFoot = m_ViewModeContext == camControlHelperMetadataViewMode::ON_FOOT;
const bool bIsInMultiplayer = NetworkInterface::IsGameInProgress();
const bool bIsSpecialAbilityRequested = control.GetPedSpecialAbility().IsDown();
const bool bPreventLookBehindForSpecialAbility = bIsOnFoot && bIsInMultiplayer && bIsSpecialAbilityRequested;
if( !bPreventLookBehindForSpecialAbility &&
((!bIsFlyingVehicleArmed && lookBehindControl.IsDown() && lookBehindControl.WasDown()) ||
(bIsFlyingVehicleArmed && lookBehindControl.HistoryHeldDown(LOOK_BEHIND_HELD_TIME))) )
{
m_IsLookingBehind = true;
m_TimeReleasedLookBehindInput = 0;
}
else
{
if(m_IsLookingBehind)
{
if(m_TimeReleasedLookBehindInput == 0)
{
m_TimeReleasedLookBehindInput = fwTimer::GetTimeInMilliseconds();
}
if(fwTimer::GetTimeInMilliseconds() >= (m_TimeReleasedLookBehindInput + m_Metadata.m_LookBehindOutroTimeMS) )
{
m_IsLookingBehind = false;
}
}
}
}
if (!isEarlyUpdate)
{
m_ShouldIgnoreLookBehindInputThisUpdate = false;
}
}
bool camControlHelper::WillBeLookingBehindThisUpdate(const CControl& control) const
{
const bool isUsingLookBehindInput = IsUsingLookBehindInput();
if (!isUsingLookBehindInput)
{
return m_IsLookingBehind;
}
bool isLookingBehind = m_IsLookingBehind;
const bool bIsInFlyingVehicle = (m_ViewModeContext == camControlHelperMetadataViewMode::IN_AIRCRAFT) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::IN_HELI);
const bool bIsInVehicle = (m_ViewModeContext == camControlHelperMetadataViewMode::IN_VEHICLE) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::ON_BIKE) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::IN_BOAT) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::IN_SUBMARINE) ||
bIsInFlyingVehicle;
bool bIsFlyingVehicleArmed = false;
if(bIsInFlyingVehicle)
{
const CPed* pPlayer = CGameWorld::FindLocalPlayer();
if(pPlayer && pPlayer->GetIsDrivingVehicle() && !pPlayer->GetIsDeadOrDying())
{
const CWeaponInfo *pCurrentWeaponInfo = pPlayer->GetEquippedWeaponInfo();
bIsFlyingVehicleArmed = pCurrentWeaponInfo && pCurrentWeaponInfo->GetIsVehicleWeapon();
}
}
const ioValue &lookBehindControl = bIsInVehicle ? control.GetVehicleLookBehind() : control.GetPedLookBehind();
if( (!bIsFlyingVehicleArmed && lookBehindControl.IsDown() && lookBehindControl.WasDown()) ||
( bIsFlyingVehicleArmed && lookBehindControl.HistoryHeldDown(LOOK_BEHIND_HELD_TIME)) )
{
isLookingBehind = true;
}
else if(isLookingBehind)
{
u32 timeReleasedLookBehindInput = m_TimeReleasedLookBehindInput;
if(timeReleasedLookBehindInput == 0)
{
timeReleasedLookBehindInput = fwTimer::GetTimeInMilliseconds();
}
if(fwTimer::GetTimeInMilliseconds() >= (timeReleasedLookBehindInput + m_Metadata.m_LookBehindOutroTimeMS) )
{
isLookingBehind = false;
}
}
return isLookingBehind;
}
void camControlHelper::UpdateViewModeControl(const CControl& control, const CEntity& FPS_MODE_SUPPORTED_ONLY(attachParent))
{
const s32 initialViewMode = m_ViewMode;
#if FPS_MODE_SUPPORTED
// TODO: rename PREF_FPS_PERSISTANT_VIEW? maybe someday...
const bool bAllowIndependentViewModes = (CPauseMenu::GetMenuPreference( PREF_FPS_PERSISTANT_VIEW ) == TRUE);
if(!bAllowIndependentViewModes)
{
ms_MaintainCurrentViewMode = true;
if( m_ViewModeContext == camControlHelperMetadataViewMode::IN_TURRET &&
m_ViewModeContext != m_Metadata.m_ViewModes.m_Context )
{
if( (camInterface::GetGameplayDirector().IsUsingVehicleTurret(true) && attachParent.GetIsTypePed()) ||
(camInterface::GetGameplayDirector().IsUsingVehicleTurret(false) && attachParent.GetIsTypeVehicle()) )
{
}
else
{
// If we are using turret context, but player is no longer in turret seat, update view mode context.
InitViewModeContext(attachParent);
}
}
}
else if(bAllowIndependentViewModes)
{
ms_MaintainCurrentViewMode = false;
ms_ContextToForceFirstPersonCamera = -1;
ms_ContextToForceThirdPersonCamera = -1;
ms_FirstPersonCancelledForCinematicCamera = false;
}
#endif
const bool shouldUpdateViewMode = ComputeShouldUpdateViewMode();
//m_IsOverridingViewModeForSpectatorModeThisUpdate allow the cover cam to drive a special selection of the spectator cam.
if(shouldUpdateViewMode || m_IsOverridingViewModeForSpectatorModeThisUpdate)
{
s32* viewModeForContext = GetViewModeForContextWithValidation(m_ViewModeContext);
if(!viewModeForContext)
{
cameraDebugf3("View-mode update: bypassing (invalid view mode for context: %s)", (m_ViewModeContext >= 0 && m_ViewModeContext < camControlHelperMetadataViewMode::eViewModeContext_NUM_ENUMS) ? PARSER_ENUM(camControlHelperMetadataViewMode__eViewModeContext).m_Names[m_ViewModeContext] : "UNKNOWN");
}
else
{
if(m_ShouldIgnoreViewModeInputThisUpdate)
{
cameraDebugf3("View-mode update: bypassing (m_ShouldIgnoreViewModeInputThisUpdate)");
}
s32 desiredViewMode = *viewModeForContext;
#if FPS_MODE_SUPPORTED
if(ms_ContextToForceFirstPersonCamera == m_ViewModeContext || ms_ContextToForceThirdPersonCamera == m_ViewModeContext)
{
// When forcing first person camera, m_ViewMode can be different than the one set for the current context.
desiredViewMode = m_ViewMode;
}
#endif
bool bIgnoreInput = false;
#if FPS_MODE_SUPPORTED
CPed* pLocalPlayerPed = CGameWorld::FindLocalPlayer();
bool bCameraDisabled = pLocalPlayerPed && pLocalPlayerPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_DISABLE_CAMERA_VIEW_MODE_CYCLE);
if(bCameraDisabled)
{
m_LastTimeViewModeControlDisabled = fwTimer::GetTimeInMilliseconds();
}
static dev_u32 s_ViewModeDisabledTimeMs = 300;
if((m_LastTimeViewModeControlDisabled != 0) &&
fwTimer::GetTimeInMilliseconds() < (m_LastTimeViewModeControlDisabled + s_ViewModeDisabledTimeMs))
{
bIgnoreInput = true;
}
#endif // FPS_MODE_SUPPORTED
//NOTE: We now use a short-press/tap view mode input in both SP and MP. This was required only to support the long-press/hold input used by the
//'interaction menu' in MP at launch.
bool isNextCameraModePressed = !bIgnoreInput && !m_ShouldIgnoreViewModeInputThisUpdate && control.GetNextCameraMode().IsEnabled() && control.GetNextCameraMode().IsReleased()
&& !control.GetNextCameraMode().IsReleasedAfterHistoryHeldDown(m_Metadata.m_MaxDurationForMultiplayerViewModeActivation)
WIN32PC_ONLY(&& !(control.GetFrontendSelect().IsDown() && control.GetFrontendRB().IsDown()) && !CLiveManager::IsSystemUiShowing()); // the global SCUI controller hotkey on PC is Back+RB
if(isNextCameraModePressed)
{
cameraDebugf3("View-mode update: INPUT_NEXT_CAMERA is pressed");
if(m_IsOverridingViewModeForSpectatorModeThisUpdate)
{
if(desiredViewMode == camControlHelperMetadataViewMode::CINEMATIC)
{
desiredViewMode = camControlHelperMetadataViewMode::THIRD_PERSON_MEDIUM;
}
else
{
desiredViewMode = camControlHelperMetadataViewMode::CINEMATIC;
}
}
else
{
bool bSelectNextViewMode = true;
#if FPS_MODE_SUPPORTED
if(m_Metadata.m_ViewModes.m_ShouldToggleViewModeBetweenThirdAndFirstPerson && camInterface::GetGameplayDirector().IsFirstPersonModeEnabled())
{
desiredViewMode = (desiredViewMode == camControlHelperMetadataViewMode::FIRST_PERSON) ? camControlHelperMetadataViewMode::THIRD_PERSON_MEDIUM : camControlHelperMetadataViewMode::FIRST_PERSON;
bSelectNextViewMode = false;
}
#endif // FPS_MODE_SUPPORTED
if (bSelectNextViewMode)
{
desiredViewMode = (desiredViewMode + 1) % camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS;
}
#if FPS_MODE_SUPPORTED
TUNE_GROUP_BOOL(FIRST_PERSON_TUNE, ENABLE_PREVENT_NARRATIVE_SWITCH, true);
if (ENABLE_PREVENT_NARRATIVE_SWITCH)
{
TUNE_GROUP_BOOL(FIRST_PERSON_TUNE, FORCE_DISABLE_SWITCHING_NARRATIVE_CAMERA_MODE, false);
if (FORCE_DISABLE_SWITCHING_NARRATIVE_CAMERA_MODE || (pLocalPlayerPed && pLocalPlayerPed->GetPlayerResetFlag(CPlayerResetFlags::PRF_DISABLE_CAMERA_VIEW_MODE_CYCLE)))
{
// Keep current view mode
desiredViewMode = initialViewMode;
isNextCameraModePressed = false;
}
}
#endif // FPS_MODE_SUPPORTED
}
}
//NOTE: We must validate the view mode, even in the absence of control input, as we might have picked up an incompatible mode externally.
ValidateViewMode(desiredViewMode);
#if FPS_MODE_SUPPORTED
if (isNextCameraModePressed)
{
bool b;
if(desiredViewMode == camControlHelperMetadataViewMode::FIRST_PERSON && pLocalPlayerPed && (pLocalPlayerPed->m_nFlags.bIsOnFire || !camInterface::GetGameplayDirector().ComputeIsFirstPersonModeAllowed(pLocalPlayerPed, false, b)))
{
// If changing modes while player is on fire, skip first person.
desiredViewMode = (desiredViewMode + 1) % camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS;
ValidateViewMode(desiredViewMode);
}
// B*2092381: Don't set the "PRF_CAMERA_VIEW_MODE_SWITCHED_TO_OR_FROM_FIRST_PERSON" flag if we're toggling view modes whilst looking behind as we skip these transitions.
bool bSetSwitchedToOrFromFPSFlag = true;
if(IsLookingBehind() )
{
if(desiredViewMode == camControlHelperMetadataViewMode::FIRST_PERSON)
{
//skip first person.
desiredViewMode = (desiredViewMode + 1) % camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS;
bSetSwitchedToOrFromFPSFlag = false;
}
else if(initialViewMode == camControlHelperMetadataViewMode::FIRST_PERSON)
{
desiredViewMode = initialViewMode;
isNextCameraModePressed = false;
bSetSwitchedToOrFromFPSFlag = false;
}
}
if ((desiredViewMode == camControlHelperMetadataViewMode::FIRST_PERSON || initialViewMode == camControlHelperMetadataViewMode::FIRST_PERSON) && bSetSwitchedToOrFromFPSFlag)
{
if(pLocalPlayerPed->GetPlayerInfo() && CPlayerInfo::IsFirstPersonModeSupportedForPed(*pLocalPlayerPed))
{
pLocalPlayerPed->GetPlayerInfo()->SetSwitchedToOrFromFirstPerson(desiredViewMode == camControlHelperMetadataViewMode::FIRST_PERSON);
}
}
}
// When changing view modes, treat as if we are overriding view mode so we know that a context is being overridden.
if(ms_MaintainCurrentViewMode)
{
if( isNextCameraModePressed ||
(ms_ContextToForceFirstPersonCamera == -1 && ms_ContextToForceThirdPersonCamera == -1) )
{
if(desiredViewMode == camControlHelperMetadataViewMode::FIRST_PERSON)
{
ms_ContextToForceFirstPersonCamera = m_ViewModeContext;
ms_ContextToForceThirdPersonCamera = -1;
ms_FirstPersonCancelledForCinematicCamera = false;
}
else if(desiredViewMode != camControlHelperMetadataViewMode::CINEMATIC)
{
ms_ContextToForceFirstPersonCamera = -1;
ms_ContextToForceThirdPersonCamera = m_ViewModeContext;
ms_ThirdPersonViewModeToForce = desiredViewMode;
ms_FirstPersonCancelledForCinematicCamera = false;
}
}
}
#endif // FPS_MODE_SUPPORTED
m_ViewMode = ms_LastSetViewMode = desiredViewMode;
#if FPS_MODE_SUPPORTED
// If we are overriding view mode, do not overwrite the context value.
if( isNextCameraModePressed ||
(ms_ContextToForceFirstPersonCamera != m_ViewModeContext && ms_ContextToForceThirdPersonCamera != m_ViewModeContext) )
#endif
{
*viewModeForContext = desiredViewMode;
}
}
}
//Update view mode blend levels.
if(m_IsLookingBehind != m_WasLookingBehind)
{
m_ShouldSkipViewModeBlendThisUpdate = true;
}
const bool hasChangedViewMode = (m_ViewMode != initialViewMode);
float viewModeDestinationBlendLevels[camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS];
sysMemSet(viewModeDestinationBlendLevels, 0, camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS * sizeof(float));
viewModeDestinationBlendLevels[m_ViewMode] = 1.0f;
if(m_ViewModeBlendEnvelope)
{
if(m_ShouldSkipViewModeBlendThisUpdate)
{
sysMemCpy(m_ViewModeBlendLevels, viewModeDestinationBlendLevels, camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS * sizeof(float));
m_ViewModeBlendEnvelope->Stop();
}
else
{
if(hasChangedViewMode)
{
sysMemCpy(m_ViewModeSourceBlendLevels, m_ViewModeBlendLevels, camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS * sizeof(float));
m_ViewModeBlendEnvelope->Start();
}
if(m_ViewModeBlendEnvelope->IsActive())
{
//NOTE: We maintain full blend after the envelope has released.
const float envelopeLevel = m_ViewModeBlendEnvelope->Update();
const bool isEnvelopeActive = m_ViewModeBlendEnvelope->IsActive();
const float blendLevel = isEnvelopeActive ? envelopeLevel : 1.0f;
for(int viewModeIndex=0; viewModeIndex<camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS; viewModeIndex++)
{
m_ViewModeBlendLevels[viewModeIndex] = Lerp(blendLevel, m_ViewModeSourceBlendLevels[viewModeIndex], viewModeDestinationBlendLevels[viewModeIndex]);
}
}
}
}
else if(hasChangedViewMode)
{
sysMemCpy(m_ViewModeBlendLevels, viewModeDestinationBlendLevels, camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS * sizeof(float));
}
m_ShouldUpdateViewModeThisUpdate = false;
m_ShouldIgnoreViewModeInputThisUpdate = false;
m_ShouldSkipViewModeBlendThisUpdate = false;
m_IsOverridingViewModeForSpectatorModeThisUpdate = false;
}
bool camControlHelper::ComputeShouldUpdateViewMode() const
{
if(!m_Metadata.m_ViewModes.m_ShouldUseViewModeInput || !m_ShouldUpdateViewModeThisUpdate)
{
if(!m_Metadata.m_ViewModes.m_ShouldUseViewModeInput)
{
cameraDebugf3("View-mode update: bypassing (!m_ShouldUseViewModeInput)");
}
else
{
cameraDebugf3("View-mode update: bypassing (!m_ShouldUpdateViewModeThisUpdate)");
}
return false;
}
if((m_ViewModeContext == camControlHelperMetadataViewMode::IN_VEHICLE) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::ON_BIKE) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::IN_BOAT) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::IN_AIRCRAFT) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::IN_SUBMARINE) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::IN_HELI) ||
(m_ViewModeContext == camControlHelperMetadataViewMode::IN_TURRET))
{
//NOTE: We only update the in-vehicle view modes when the player is fully inside the vehicle.
camGameplayDirector& gameplayDirector = camInterface::GetGameplayDirector();
const CVehicle* followVehicle = gameplayDirector.GetFollowVehicle();
const s32 vehicleEntryExitState = gameplayDirector.GetVehicleEntryExitState();
if(!followVehicle || (vehicleEntryExitState != camGameplayDirector::INSIDE_VEHICLE))
{
cameraDebugf3("View-mode update: bypassing (in-vehicle context, but not in a vehicle)");
return false;
}
}
//NOTE: We bypass the view mode update if the cinematic director is rendering irrespective of the current view mode, to avoid confusing
//the user.
const camCinematicDirector& cinematicDirector = camInterface::GetCinematicDirector();
const bool isCinematicDirectorRendering = cinematicDirector.IsRendering();
if(isCinematicDirectorRendering)
{
const camBaseCinematicContext* activeCinematicContext = cinematicDirector.GetCurrentContext();
if(activeCinematicContext && activeCinematicContext->IsValid(true, false))
{
cameraDebugf3("View-mode update: bypassing (cinematic director is rendering)");
return false;
}
}
return true;
}
void camControlHelper::ValidateViewMode(s32& viewMode) const
{
for(s32 i=0; i<camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS; i++)
{
const bool isModeValid = ComputeIsViewModeValid(viewMode);
if(isModeValid)
{
return;
}
viewMode = (viewMode + 1) % camControlHelperMetadataViewMode::eViewMode_NUM_ENUMS;
}
//There are no valid view modes, so fall-back to the default mode.
viewMode = g_DefaultViewMode;
}
bool camControlHelper::ComputeIsViewModeValid(s32 viewMode) const
{
//Ensure that the on-foot view mode context always supports cinematic view mode in spectator mode. The follow parachute control helper was not
//flagged as supporting the cinematic view mode and it is not possible to modify the camera metadata for the Online title update.
if(NetworkInterface::IsInSpectatorMode() && (m_ViewModeContext == camControlHelperMetadataViewMode::ON_FOOT) &&
(viewMode == camControlHelperMetadataViewMode::CINEMATIC))
{
return true;
}
bool isValid = m_Metadata.m_ViewModes.m_Flags.IsSet(viewMode);
#if FPS_MODE_SUPPORTED
if( camInterface::GetGameplayDirector().IsFirstPersonModeEnabled() && viewMode == camControlHelperMetadataViewMode::FIRST_PERSON )
{
// For now, force first person mode as valid for all control helpers, if enabled.
isValid = true;
}
#endif
if(isValid)
{
switch(m_ViewModeContext)
{
case camControlHelperMetadataViewMode::IN_VEHICLE:
case camControlHelperMetadataViewMode::ON_BIKE:
case camControlHelperMetadataViewMode::IN_BOAT:
case camControlHelperMetadataViewMode::IN_AIRCRAFT:
case camControlHelperMetadataViewMode::IN_SUBMARINE:
case camControlHelperMetadataViewMode::IN_HELI:
case camControlHelperMetadataViewMode::IN_TURRET:
{
//If we have a follow vehicle, check that it supports this view mode.
const CVehicle* followVehicle = camInterface::GetGameplayDirector().GetFollowVehicle();
const CVehicleModelInfo* followVehicleModelInfo = followVehicle ? followVehicle->GetVehicleModelInfo() : NULL;
switch(viewMode)
{
case camControlHelperMetadataViewMode::FIRST_PERSON:
{
const u32 cameraHash = followVehicleModelInfo ? followVehicleModelInfo->GetBonnetCameraNameHash() : 0;
const camCinematicMountedCameraMetadata* metadata = camFactory::FindObjectMetadata<camCinematicMountedCameraMetadata>(cameraHash);
isValid = (metadata != NULL);
//NOTE: We no longer check for the bonnet mod, as the cinematic mounted camera is a POV from within the vehicle
}
break;
case camControlHelperMetadataViewMode::CINEMATIC:
isValid = followVehicleModelInfo && followVehicleModelInfo->ShouldUseCinematicViewMode() && NetworkInterface::IsInSpectatorMode();
break;
default:
break;
}
}
break;
case camControlHelperMetadataViewMode::ON_FOOT:
{
switch(viewMode)
{
case camControlHelperMetadataViewMode::CINEMATIC:
{
isValid = NetworkInterface::IsInSpectatorMode();
}
break;
#if FPS_MODE_SUPPORTED
case camControlHelperMetadataViewMode::FIRST_PERSON:
{
isValid = camInterface::GetGameplayDirector().IsFirstPersonModeEnabled();
}
break;
#endif
default:
break;
}
}
break;
default:
break;
}
}
return isValid;
}
const camControlHelperMetadataLookAround& camControlHelper::GetLookAroundMetadata() const
{
#if __BANK
if(ms_UseOverriddenLookAroundHelper)
{
return ms_OverriddenLookAroundHelper;
}
else
#endif
{
return m_Metadata.m_LookAround;
}
}
void camControlHelper::UpdateLookAroundControl(const CControl& control)
{
float lookAroundInputMag;
float lookAroundHeadingInput;
float lookAroundPitchInput;
bool usingAccelerometers = false;
#if RSG_ORBIS
m_WasPlayerAiming = m_WasPlayerAiming && !m_ShouldIgnoreLookAroundInputThisUpdate;
#endif // RSG_ORBIS
m_IsLookAroundInputPresent = ComputeLookAroundInput(control, lookAroundInputMag, lookAroundHeadingInput, lookAroundPitchInput, usingAccelerometers);
CPed* pPlayer = CGameWorld::FindLocalPlayer();
#if RSG_ORBIS
////const bool isPlayerAiming = control.GetPedTargetIsDown();
bool isPlayerAiming = false;
if(pPlayer && pPlayer->GetPlayerInfo())
{
isPlayerAiming = pPlayer->GetPlayerInfo()->IsAiming();
}
m_WasPlayerAiming = isPlayerAiming;
#endif // RSG_ORBIS
const float timeStep = GetLookAroundMetadata().m_ShouldUseGameTime ? fwTimer::GetTimeStep() : fwTimer::GetCamTimeStep();
#if RSG_PC
// We only consider the last known source as keyboard/mouse when ped look LR is enabled. The reason for this is that when
// it is not, we want the camera to follow the vehicle. We are not too worried about UD.
// NOTE: treat steam controller as mouse.
m_WasUsingKeyboardMouseLastInput = control.UseRelativeLook() && control.GetPedLookLeftRight().IsEnabled();
if ( control.IsRelativeLookSource(control.GetPedLookLeftRight().GetSource()) ||
control.IsRelativeLookSource(control.GetPedLookUpDown().GetSource()) )
{
// PC only: No acceleration or aiming assistance when using the mouse.
UpdateLookAroundInputEnvelope(m_IsLookAroundInputPresent);
const bool bAllowMouseAcceleration = (CPauseMenu::GetMenuPreference( PREF_MOUSE_ACCELERATION ) == TRUE); // aka Fine Aiming Control
if(bAllowMouseAcceleration && lookAroundInputMag >= SMALL_FLOAT && lookAroundInputMag < 1.0f)
{
float scaledLookAroundInputMag = rage::Powf(lookAroundInputMag, MOUSE_POWER_EXPONENT);
lookAroundHeadingInput *= scaledLookAroundInputMag/lookAroundInputMag;
lookAroundPitchInput *= scaledLookAroundInputMag/lookAroundInputMag;
}
m_NormalisedLookAroundHeadingInput = lookAroundHeadingInput;
m_NormalisedLookAroundPitchInput = lookAroundPitchInput;
if (pPlayer && pPlayer->GetMotionData()->GetCurrentMoveBlendRatio().Mag() > MOVEBLENDRATIO_STILL)
{
m_NormalisedLookAroundHeadingInput += (pPlayer->GetMotionData()->GetSteerBias() * timeStep);
}
// Treat mouse input as angle/frame.
// We then scale by radians/second and then by 1/60 seconds/frame to give us the target radians/frame with a mouse input of 1.0
const float c_fInputScale = (1.0f/60.0f);
float fMouseSensitivityOverride = 0.0f;
PARAM_mouse_sensitivity_override.Get(fMouseSensitivityOverride);
#if __BANK
TUNE_GROUP_FLOAT(MOUSE_TUNE, MOUSE_SENSITIVITY_OVERRIDE, -1.0f, -1.0f, 5000.0f, 1.0f);
if(MOUSE_SENSITIVITY_OVERRIDE > 0.0f)
{
fMouseSensitivityOverride = MOUSE_SENSITIVITY_OVERRIDE;
}
#endif
TUNE_GROUP_FLOAT(MOUSE_TUNE, MAX_MOUSE_SENSITIVITY_OVERRIDE, 2500.0f, 0.0f, 5000.0f, 1.0f);
fMouseSensitivityOverride = Min(fMouseSensitivityOverride, MAX_MOUSE_SENSITIVITY_OVERRIDE);
const float fMouseSensitivity = ( (fMouseSensitivityOverride > 0.0f) ? fMouseSensitivityOverride : (float)CPauseMenu::GetMenuPreference(PREF_MOUSE_ON_FOOT_SCALE)) / (float)MAX_MOUSE_SCALE;
const float fLookSensitivity = control.WasSteamControllerLastKnownSource() ? 1.0f : fMouseSensitivity;
const float fMaxHeadingSpeedPerSecond = Lerp(fLookSensitivity, m_Metadata.m_LookAround.m_MouseMaxHeadingSpeedMin, m_Metadata.m_LookAround.m_MouseMaxHeadingSpeedMax) * DtoR * c_fInputScale;
const float fMaxPitchSpeedPerSecond = Lerp(fLookSensitivity, m_Metadata.m_LookAround.m_MouseMaxPitchSpeedMin, m_Metadata.m_LookAround.m_MouseMaxPitchSpeedMax) * DtoR * c_fInputScale;
m_LookAroundHeadingOffset = m_NormalisedLookAroundHeadingInput * fMaxHeadingSpeedPerSecond;
m_LookAroundPitchOffset = m_NormalisedLookAroundPitchInput * fMaxPitchSpeedPerSecond;
TUNE_GROUP_FLOAT(MOUSE_TUNE, MAX_HEADING_TURN_RATE_PER_UPDATE, 180.0f, 45.0f, 360.0f, 1.0f);
TUNE_GROUP_FLOAT(MOUSE_TUNE, MAX_PITCH_TURN_RATE_PER_UPDATE, 90.0f, 20.0f, 180.0f, 1.0f);
m_LookAroundHeadingOffset = Clamp(m_LookAroundHeadingOffset, -MAX_HEADING_TURN_RATE_PER_UPDATE*DtoR, MAX_HEADING_TURN_RATE_PER_UPDATE*DtoR);
m_LookAroundPitchOffset = Clamp(m_LookAroundPitchOffset, -MAX_PITCH_TURN_RATE_PER_UPDATE*DtoR, MAX_PITCH_TURN_RATE_PER_UPDATE*DtoR);
}
else
#endif // RSG_PC
{
UpdateLookAroundInputEnvelope(m_IsLookAroundInputPresent);
//Apply the custom power factor scaling specified in metadata to change the control response across the range of stick input.
float scaledLookAroundInputMag = rage::Powf(lookAroundInputMag, GetLookAroundMetadata().m_InputMagPowerFactor);
// CS: Haxx code - sorry
if(pPlayer)
{
pPlayer->GetPlayerInfo()->GetTargeting().GetScaledLookAroundInputMagForCamera(lookAroundInputMag, scaledLookAroundInputMag);
}
float c_Deceleration = (!m_UseLeftStickForLookInputThisUpdate || GetLookAroundMetadata().m_LSDeceleration == 0.0f) ?
GetLookAroundMetadata().m_Deceleration : GetLookAroundMetadata().m_LSDeceleration;
float c_Acceleration = (!m_UseLeftStickForLookInputThisUpdate || GetLookAroundMetadata().m_LSAcceleration == 0.0f) ?
GetLookAroundMetadata().m_Acceleration : GetLookAroundMetadata().m_LSAcceleration;
float fMaxSpeed = 1.0f;
#if FPS_MODE_SUPPORTED
// Use slower accel/max speed values for FPS underwater swim-sprinting
if (pPlayer && pPlayer->GetIsFPSSwimming() && m_UseLeftStickForLookInputThisUpdate)
{
static dev_float fAccel = 1.0f;
c_Acceleration = fAccel;
static dev_float fMaxSpeedTune = 0.33f;
fMaxSpeed = fMaxSpeedTune;
}
if( m_UseLeftStickForLookInputThisUpdate && !m_UseBothSticksForLookInputThisUpdate &&
(lookAroundHeadingInput == 0.0f || m_LookAroundHeadingInputPreviousUpdate*lookAroundHeadingInput < 0.0f) )
{
if ((pPlayer && !pPlayer->GetIsFPSSwimmingUnderwater()) || (pPlayer && pPlayer->GetIsFPSSwimmingUnderwater() && lookAroundPitchInput == 0.0f))
{
// Reset speed if input is within the deadzone or if direction changed in one update.
m_LookAroundSpeed = 0.0f;
}
}
m_LookAroundHeadingInputPreviousUpdate = lookAroundHeadingInput;
#endif
s32 accelProfileSetting = (CProfileSettings::GetInstance().Exists(CProfileSettings::AIM_ACCELERATION) ?
CProfileSettings::GetInstance().GetInt(CProfileSettings::AIM_ACCELERATION) : g_DefaultControllerAcceleration);
#if FPS_MODE_SUPPORTED
//TODO: Determine if using a first person control helper via flag instead of hardcoding by metadata name?
bool bUseFpsSettings = (
m_Metadata.m_Name == ATSTRINGHASH("FIRST_PERSON_SHOOTER_NO_AIM_CONTROL_HELPER", 0x488A30D2) ||
m_Metadata.m_Name == ATSTRINGHASH("DEFAULT_FIRST_PERSON_AIM_CONTROL_HELPER", 0x8BB92CE8) ||
m_Metadata.m_Name == ATSTRINGHASH("DEFAULT_VEHICLE_POV_CONTROL_HELPER", 0x49f5edae) ||
m_Metadata.m_Name == ATSTRINGHASH("POV_TURRET_CONTROL_HELPER", 0x3cc86ec0)
);
if(bUseFpsSettings)
{
accelProfileSetting = (CProfileSettings::GetInstance().Exists(CProfileSettings::FPS_AIM_ACCELERATION) ?
CProfileSettings::GetInstance().GetInt(CProfileSettings::FPS_AIM_ACCELERATION) : g_DefaultControllerAcceleration);
}
#endif
{
const camControlHelperMetaDataPrecisionAimSettings &precisionAim = GetLookAroundMetadata().m_PrecisionAimSettings;
float fProfileScale = Clamp((float)accelProfileSetting / (float)g_OriginalMaxControllerAcceleration, 0.0f, 1.0f);
float fAccelScale = rage::Lerp(fProfileScale, precisionAim.m_MinAccelModifier, precisionAim.m_MaxAccelModifier);
c_Acceleration *= fAccelScale;
float fDeccelScale = rage::Lerp(fProfileScale, precisionAim.m_MinDeccelModifier, precisionAim.m_MaxDeccelModifier );
//! Note: We never allow this to drop below 1.0f. min value allows us to keep the mid range profile value the same as when we shipped.
//! e.g. g_DefaultControllerAcceleration / g_OriginalMaxControllerAcceleration == 1.0f.
fDeccelScale = Max(fDeccelScale, 1.0f);
c_Deceleration *= fDeccelScale;
}
//Apply acceleration and deceleration to stick input.
const float decelerationToApply = m_LookAroundDecelerationScaling * c_Deceleration;
float baseLookAroundOffset = ComputeOffsetBasedOnAcceleratedInput(scaledLookAroundInputMag, c_Acceleration,
decelerationToApply, fMaxSpeed, m_LookAroundSpeed, timeStep);
//Scale the base offset in accordance with the aim sensitivity, thereby affecting acceleration/deceleration and maximum speed.
float aimSensitivityScaling = GetAimSensitivityScaling();
baseLookAroundOffset *= aimSensitivityScaling;
#if __BANK
baseLookAroundOffset *= ms_LookAroundSpeedScalar;
#endif
static const atHashString nightClubControlHelperName(ATSTRINGHASH("NIGHTCLUB_FOLLOW_PED_CONTROL_HELPER", 0xE69EFD48));
const bool isRunningTheNightClubDanceCamera = m_Metadata.m_Name == nightClubControlHelperName;
if(lookAroundInputMag > 0.0f)
{
//Normalise the look-around heading and pitch inputs, or use the last valid normalised values. This allows us to apply an appropriate deceleration
//across the two axes.
m_NormalisedLookAroundHeadingInput = lookAroundHeadingInput / lookAroundInputMag;
m_NormalisedLookAroundPitchInput = lookAroundPitchInput / lookAroundInputMag;
}
else if(isRunningTheNightClubDanceCamera)
{
//special case for the nightclub dancing camera, we do this to make sure we apply deceleration.
if(Abs(baseLookAroundOffset) < SMALL_FLOAT)
{
m_NormalisedLookAroundHeadingInput = 0.0f;
m_NormalisedLookAroundPitchInput = 0.0f;
}
}
else
{
m_NormalisedLookAroundHeadingInput = 0.0f;
m_NormalisedLookAroundPitchInput = 0.0f;
}
if (pPlayer && pPlayer->GetMotionData()->GetCurrentMoveBlendRatio().Mag() > MOVEBLENDRATIO_STILL)
{
m_NormalisedLookAroundHeadingInput = Clamp(m_NormalisedLookAroundHeadingInput + pPlayer->GetMotionData()->GetSteerBias(), -1.f, 1.f);
}
float fMaxHeadingSpeed = GetLookAroundMetadata().m_MaxHeadingSpeed;
float fMaxPitchSpeed = GetLookAroundMetadata().m_MaxPitchSpeed;
#if FPS_MODE_SUPPORTED
// If player is FPS swimming, scale the heading/pitch offset down by half (and clamp).
if (pPlayer && pPlayer->GetIsFPSSwimming())
{
static dev_float fMaxHeadingSpeedSwimTune = 75.0f;
static dev_float fMaxPitchSpeedSwimTune = 65.0f;
static dev_float fSwimScaler = 0.75f;
fMaxHeadingSpeed = Clamp(fMaxHeadingSpeed * fSwimScaler, 25.0f, fMaxHeadingSpeedSwimTune);
fMaxPitchSpeed = Clamp(fMaxPitchSpeed * fSwimScaler, 25.0f, fMaxPitchSpeedSwimTune);
}
#endif //FPS_MODE_SUPPORTED
m_LookAroundHeadingOffset = m_NormalisedLookAroundHeadingInput * baseLookAroundOffset * fMaxHeadingSpeed * DtoR;
m_LookAroundPitchOffset = m_NormalisedLookAroundPitchInput * baseLookAroundOffset * fMaxPitchSpeed * DtoR;
}
m_ShouldIgnoreLookAroundInputThisUpdate = false;
}
//Check if we have look-around input, using a dead-zone to prevent false hits.
bool camControlHelper::ComputeLookAroundInput(const CControl& control, float& inputMag, float& headingInput, float& pitchInput, bool& ORBIS_ONLY(usingAccelerometers)) const
{
if(m_ShouldIgnoreLookAroundInputThisUpdate)
{
headingInput = 0.0f;
pitchInput = 0.0f;
inputMag = 0.0f;
}
else
{
const ioValue& headingControl = (!m_UseLeftStickForLookInputThisUpdate) ? control.GetPedAimWeaponLeftRight() : control.GetPedWalkLeftRight();
const ioValue& pitchControl = (!m_UseLeftStickForLookInputThisUpdate) ? control.GetPedAimWeaponUpDown() : control.GetPedWalkUpDown();
CPed* pPlayer = CGameWorld::FindLocalPlayer();
#if RSG_ORBIS
////const bool isPlayerAiming = control.GetPedTargetIsDown();
bool isPlayerAiming = false;
if(pPlayer && pPlayer->GetPlayerInfo())
{
isPlayerAiming = pPlayer->GetPlayerInfo()->IsAiming();
}
#endif // RSG_ORBIS
//NOTE: Each input is zeroed if the corresponding speed limit is zero and hence the input should be ignored.
//NOTE: We apply no dead-zoning to the individual heading and pitch axes, but instead apply an optional round dead-zone below.
ioValue::ReadOptions options = ioValue::NO_DEAD_ZONE;
headingInput = IsNearZero(GetLookAroundMetadata().m_MaxHeadingSpeed) ? 0.0f : -headingControl.GetUnboundNorm(options);
pitchInput = IsNearZero(GetLookAroundMetadata().m_MaxPitchSpeed) ? 0.0f : -pitchControl.GetUnboundNorm(options);
// Script-requested ability to invert look controls as a MP pickup power
if (pPlayer && pPlayer->GetPedResetFlag(CPED_RESET_FLAG_InvertLookAroundControls) && !m_UseLeftStickForLookInputThisUpdate)
{
headingInput = -headingInput;
pitchInput = -pitchInput;
}
inputMag = rage::Sqrtf((headingInput * headingInput) + (pitchInput * pitchInput));
float fDeadZone = GetDeadZoneScaling(inputMag);
ioValue::ReadOptions deadZoneOptions = ioValue::DEFAULT_OPTIONS;
deadZoneOptions.m_DeadZone = fDeadZone;
#if RSG_PC
TUNE_GROUP_FLOAT(CAM_FPS, c_fLeftStickMouseInputMax, 0.50f, 0.0f, 1.0f, 0.001f);
#endif
if( ioValue::RequiresDeadZone(headingControl.GetSource()) )
{
// Re-read input with deadzone and clear un-deadzoned input if deadzoned input is zero.
float deadzonedInput = -headingControl.GetUnboundNorm(deadZoneOptions);
if(deadzonedInput == 0.0f)
headingInput = 0.0f;
}
#if RSG_PC
else if(m_UseLeftStickForLookInputThisUpdate)
{
headingInput = Clamp(headingInput, -c_fLeftStickMouseInputMax, c_fLeftStickMouseInputMax);
}
#endif
if( ioValue::RequiresDeadZone(pitchControl.GetSource()) )
{
// Re-read input with deadzone and clear un-deadzoned input if deadzoned input is zero.
float deadzonedInput = -pitchControl.GetUnboundNorm(deadZoneOptions);
if(deadzonedInput == 0.0f)
pitchInput = 0.0f;
}
#if RSG_PC
else if(m_UseLeftStickForLookInputThisUpdate)
{
pitchInput = Clamp(pitchInput, -c_fLeftStickMouseInputMax, c_fLeftStickMouseInputMax);
}
#endif
#if RSG_PC
// If either axis is using the mouse, disable the dead-zone.
if ( !ioValue::RequiresDeadZone(headingControl.GetSource()) ||
!ioValue::RequiresDeadZone(pitchControl.GetSource()) )
{
inputMag = rage::Sqrtf((headingInput * headingInput) + (pitchInput * pitchInput));
}
else
#endif // RSG_PC
{
#if RSG_ORBIS
float motionPitch = 0.0f;
float motionYaw = 0.0f;
if (isPlayerAiming)
{
Quaternion controllerOrientation;
if (const_cast<CControl&>(control).GetOrientation(controllerOrientation))
{
// Convert quaternion to euler angles to get pitch and yaw relative to centered orientation.
Vector3 vecEuler;
controllerOrientation.ToEulers(vecEuler, eEulerOrderXYZ);
motionPitch = vecEuler.x;
motionYaw = vecEuler.y;
}
}
#endif // RSG_ORBIS
inputMag = rage::ioAddRoundDeadZone(headingInput, pitchInput, fDeadZone);
#if FPS_MODE_SUPPORTED
if(m_UseBothSticksForLookInputThisUpdate && !m_UseLeftStickForLookInputThisUpdate && inputMag == 0.0f)
{
const ioValue& headingLeftStick = control.GetPedWalkLeftRight();
const ioValue& pitchLeftStick = control.GetPedWalkUpDown();
headingInput = IsNearZero(GetLookAroundMetadata().m_MaxHeadingSpeed) ? 0.0f : -headingLeftStick.GetUnboundNorm(options);
pitchInput = IsNearZero(GetLookAroundMetadata().m_MaxPitchSpeed) ? 0.0f : -pitchLeftStick.GetUnboundNorm(options);
if( ioValue::RequiresDeadZone(headingLeftStick.GetSource()) )
{
// Re-read input with deadzone and clear un-deadzoned input if deadzoned input is zero.
float deadzonedInput = -headingLeftStick.GetUnboundNorm();
if(deadzonedInput == 0.0f)
headingInput = 0.0f;
}
if( ioValue::RequiresDeadZone(pitchLeftStick.GetSource()) )
{
// Re-read input with deadzone and clear un-deadzoned input if deadzoned input is zero.
float deadzonedInput = -pitchLeftStick.GetUnboundNorm();
if(deadzonedInput == 0.0f)
pitchInput = 0.0f;
}
inputMag = rage::ioAddRoundDeadZone(headingInput, pitchInput, ioValue::DEFAULT_DEAD_ZONE_VALUE);
}
#endif
#if FPS_MODE_SUPPORTED
if(m_UseLeftStickForLookInputThisUpdate && !m_UseBothSticksForLookInputThisUpdate)
{
// Apply angular dead zone.
const float c_fAngleDeadzoneRad = GetLookAroundMetadata().m_LSDeadZoneAngle * DtoR;
float fStickDirection = Atan2f(headingInput, pitchInput);
if( Abs(fStickDirection) < c_fAngleDeadzoneRad )
{
headingInput = 0.0f;
}
else
{
// Don't re-scale input for FPS swimming underwater (as we use both heading and pitch)
if (pPlayer && !pPlayer->GetIsFPSSwimmingUnderwater())
{
// Re-scale input
float boundary = (fStickDirection > 0.0f) ? -c_fAngleDeadzoneRad : c_fAngleDeadzoneRad;
headingInput = (fStickDirection - boundary) / (PI*0.50f);
headingInput = Clamp(headingInput, -1.0f, 1.0f);
}
}
}
#endif // FPS_MODE_SUPPORTED
#if RSG_ORBIS
#if !CONTROLLER_ORIENTATION_ADD_TO_STICK_INPUT
if (isPlayerAiming && inputMag == 0.0f)
#else
if (isPlayerAiming)
#endif
{
{
// Add deadzone
////s32 viewMode = GetViewModeForContext(m_ViewModeContext);
////static float c_fMotionDeadZone = ((viewMode != camControlHelperMetadataViewMode::FIRST_PERSON) ? 0.05f : 0.0f);
rage::ioAddRoundDeadZone(motionYaw, motionPitch, c_fMotionDeadzone * DtoR);
}
if (motionYaw != 0.0f || motionPitch != 0.0f)
{
#if !CONTROLLER_ORIENTATION_ADD_TO_STICK_INPUT
usingAccelerometers = true;
pitchInput = motionPitch * c_fMotionVerticalScale;
headingInput = motionYaw * c_fMotionHorizontalScale;
#else
// When adding to stick input, treat accelerometer input the same.
pitchInput += motionPitch * c_fMotionVerticalScale;
headingInput += motionYaw * c_fMotionHorizontalScale;
#endif
// Deadzone already applied for stick input and for controller orientation, so just re-calculate the magnitude.
Vector3 vTemp(headingInput, pitchInput, 0.0f);
inputMag = vTemp.Mag();
}
}
#endif // RSG_ORBIS
}
}
bool isInputPresent = (inputMag > 0.0f);
return isInputPresent;
}
void camControlHelper::UpdateLookAroundInputEnvelope(bool isInputPresent)
{
if(m_LookAroundInputEnvelope)
{
//Apply an envelope to the look-around input presence. This can be used to gradually blend out camera behaviors when look-around input is present.
#if RSG_PC
if (m_WasUsingKeyboardMouseLastInput)
{
if (isInputPresent)
{
m_LookAroundInputEnvelope->OverrideAttackDuration(0);
m_LookAroundInputEnvelope->AutoStart();
}
}
else
#endif // RSG_PC
{
m_LookAroundInputEnvelope->AutoStartStop(isInputPresent);
}
m_LookAroundInputEnvelopeLevel = m_LookAroundInputEnvelope->Update();
//TODO: Push this into the envelope helper.
m_LookAroundInputEnvelopeLevel = SlowInOut(m_LookAroundInputEnvelopeLevel);
}
else
{
//We don't have an envelope, so just use the input state directly.
m_LookAroundInputEnvelopeLevel = isInputPresent ? 1.0f : 0.0f;
}
}
float camControlHelper::GetDeadZoneScaling(float fInputMag) const
{
float fMaxDeadZone = ioValue::DEFAULT_DEAD_ZONE_VALUE;
float fDeadZone = fMaxDeadZone;
s32 profileSetting = (CProfileSettings::GetInstance().Exists(CProfileSettings::AIM_DEADZONE) ?
CProfileSettings::GetInstance().GetInt(CProfileSettings::AIM_DEADZONE) : g_DefaultControllerDeadZone);
#if FPS_MODE_SUPPORTED
//TODO: Determine if using a first person control helper via flag instead of hardcoding by metadata name?
bool bUseFpsSettings = (
m_Metadata.m_Name == ATSTRINGHASH("FIRST_PERSON_SHOOTER_NO_AIM_CONTROL_HELPER", 0x488A30D2) ||
m_Metadata.m_Name == ATSTRINGHASH("DEFAULT_FIRST_PERSON_AIM_CONTROL_HELPER", 0x8BB92CE8) ||
m_Metadata.m_Name == ATSTRINGHASH("DEFAULT_VEHICLE_POV_CONTROL_HELPER", 0x49f5edae)
);
if(bUseFpsSettings)
{
profileSetting = (CProfileSettings::GetInstance().Exists(CProfileSettings::FPS_AIM_DEADZONE) ?
CProfileSettings::GetInstance().GetInt(CProfileSettings::FPS_AIM_DEADZONE) : g_DefaultControllerDeadZone);
}
#endif
float fScale = Clamp((float)profileSetting / (float)g_OriginalMaxControllerDeadZone, 0.0f, 1.0f);
float fMinDeadZone = ioValue::DEFAULT_MIN_DEAD_ZONE_VALUE;
fDeadZone = fMinDeadZone + ((fMaxDeadZone - fMinDeadZone) * fScale);
fInputMag = Max(fInputMag, m_LookAroundSpeed);
//! If we aggressively move stick, start to increase deadzone, so that we don't wave about on 1 axis. E.g. moving in horizontal plane only could
//! introduce judder into vertical plane, so this avoids that.
if(fInputMag > GetLookAroundMetadata().m_PrecisionAimSettings.m_InputMagToIncreaseDeadZoneMax)
{
fDeadZone = fMaxDeadZone;
}
else if(fInputMag > GetLookAroundMetadata().m_PrecisionAimSettings.m_InputMagToIncreaseDeadZoneMin)
{
float fScale = (fInputMag - GetLookAroundMetadata().m_PrecisionAimSettings.m_InputMagToIncreaseDeadZoneMin) / ((GetLookAroundMetadata().m_PrecisionAimSettings.m_InputMagToIncreaseDeadZoneMax-GetLookAroundMetadata().m_PrecisionAimSettings.m_InputMagToIncreaseDeadZoneMin));
fDeadZone = rage::Lerp(fScale, fDeadZone, fMaxDeadZone);
}
return Clamp(fDeadZone, fMinDeadZone, fMaxDeadZone);
}
float camControlHelper::GetAimSensitivityScaling() const
{
s32 aimSensitivity;
#if FPS_MODE_SUPPORTED
const bool isUsingDefaultVehiclePovControlHelper = m_Metadata.m_Name == ATSTRINGHASH("DEFAULT_VEHICLE_POV_CONTROL_HELPER", 0x49f5edae);
// TODO: Determine if using a first person control helper via flag instead of hardcoding by metadata name?
bool bUseFpsSettings = (
m_Metadata.m_Name == ATSTRINGHASH("FIRST_PERSON_SHOOTER_NO_AIM_CONTROL_HELPER", 0x488A30D2) ||
m_Metadata.m_Name == ATSTRINGHASH("DEFAULT_FIRST_PERSON_AIM_CONTROL_HELPER", 0x8BB92CE8) ||
m_Metadata.m_Name == ATSTRINGHASH("POV_TURRET_CONTROL_HELPER", 0x3cc86ec0) ||
isUsingDefaultVehiclePovControlHelper
);
const CPed* pPlayer = CGameWorld::FindLocalPlayer();
bool isPlayerAiming = false; // b*2152871 - we don't use a separate camera for in-vehicle POV aiming, so cannot branch on m_ShouldApplyAimSensitivityPref, instead we must branch on isPlayerAiming
if(pPlayer && pPlayer->GetPlayerInfo())
{
isPlayerAiming = pPlayer->GetPlayerInfo()->IsAiming();
}
#endif // FPS_MODE_SUPPORTED
if((m_Metadata.m_ShouldApplyAimSensitivityPref || m_ShouldForceAimSensitivityThisUpdate) FPS_MODE_SUPPORTED_ONLY( || (isUsingDefaultVehiclePovControlHelper && isPlayerAiming)))
{
s32 profileSetting = (CProfileSettings::GetInstance().Exists(CProfileSettings::CONTROLLER_AIM_SENSITIVITY) ?
CProfileSettings::GetInstance().GetInt(CProfileSettings::CONTROLLER_AIM_SENSITIVITY) : g_DefaultControllerAimSensitivity);
#if FPS_MODE_SUPPORTED
if(bUseFpsSettings)
{
profileSetting = (CProfileSettings::GetInstance().Exists(CProfileSettings::FPS_AIM_SENSITIVITY) ?
CProfileSettings::GetInstance().GetInt(CProfileSettings::FPS_AIM_SENSITIVITY) : g_DefaultControllerAimSensitivity);
if(m_LimitFirstPersonAimSensitivityThisUpdate >= 0)
profileSetting = Min(profileSetting, m_LimitFirstPersonAimSensitivityThisUpdate);
}
#endif
aimSensitivity = BANK_ONLY((ms_DebugAimSensitivitySetting >= 0) ? ms_DebugAimSensitivitySetting : ) profileSetting;
}
else
{
s32 profileSetting = (CProfileSettings::GetInstance().Exists(CProfileSettings::LOOK_AROUND_SENSITIVITY) ?
CProfileSettings::GetInstance().GetInt(CProfileSettings::LOOK_AROUND_SENSITIVITY) : g_DefaultControllerAimSensitivity);
#if FPS_MODE_SUPPORTED
if(bUseFpsSettings)
{
profileSetting = (CProfileSettings::GetInstance().Exists(CProfileSettings::FPS_LOOK_SENSITIVITY) ?
CProfileSettings::GetInstance().GetInt(CProfileSettings::FPS_LOOK_SENSITIVITY) : g_DefaultControllerAimSensitivity);
if(m_LimitFirstPersonAimSensitivityThisUpdate >= 0)
profileSetting = Min(profileSetting, m_LimitFirstPersonAimSensitivityThisUpdate);
}
#endif
aimSensitivity = BANK_ONLY((ms_DebugLookAroundSensitivitySetting >= 0) ? ms_DebugLookAroundSensitivitySetting : )
m_ShouldForceDefaultLookAroundSensitivityThisUpdate ? g_DefaultControllerAimSensitivity : profileSetting;
}
// B*2248229: Clamp sensitivity for turrets so they don't turn really slow at lower sensitivities.
if (camInterface::GetGameplayDirector().IsUsingVehicleTurret(false))
{
TUNE_GROUP_INT(TURRET_TUNE, iTurretMaxSensitivity, 8, 0, 14, 1);
aimSensitivity = Min(iTurretMaxSensitivity, aimSensitivity);
}
//NOTE: We are extending the upper limit of aim sensitivity post-launch, so clamp against the revised limit by default.
const s32 maxAimSensitivity = m_ShouldUseOriginalMaxControllerAimSensitivity ? g_OriginalMaxControllerAimSensitivity : g_RevisedMaxControllerAimSensitivity;
aimSensitivity = Clamp(aimSensitivity, 0, maxAimSensitivity);
const float aimSensitivityTValue = static_cast<float>(aimSensitivity) / static_cast<float>(g_OriginalMaxControllerAimSensitivity);
const float aimSensitivityScaling = Lerp(aimSensitivityTValue, m_Metadata.m_AimSensitivityScalingLimits.x, m_Metadata.m_AimSensitivityScalingLimits.y);
return aimSensitivityScaling;
}
void camControlHelper::UpdateZoomControl(CControl& control)
{
if(!m_Metadata.m_Zoom.m_ShouldUseZoomInput)
{
return;
}
float zoomInput;
ComputeZoomInput(control, zoomInput);
const float timeStep = m_Metadata.m_Zoom.m_ShouldUseGameTime ? fwTimer::GetTimeStep() : fwTimer::GetCamTimeStep();
float zoomFactorOffset = ComputeOffsetBasedOnAcceleratedInput(zoomInput, m_Metadata.m_Zoom.m_Acceleration, m_Metadata.m_Zoom.m_Deceleration,
m_Metadata.m_Zoom.m_MaxSpeed, m_ZoomFactorSpeed, timeStep);
UpdateZoomFactorLimits();
if(m_Metadata.m_Zoom.m_ShouldUseDiscreteZoomControl)
{
//Implement discrete zoom, i.e. max zoom if the zoom-in control input is applied, otherwise min zoom.
ms_ZoomFactor = (zoomFactorOffset > 0.0f) ? m_ZoomFactorLimits.y : m_ZoomFactorLimits.x;
}
else
{
//Scale the offset based upon the current zoom factor. This prevents zoom speed seeming faster towards the low end of the zoom range.
zoomFactorOffset *= ms_ZoomFactor;
ms_ZoomFactor += zoomFactorOffset;
ms_ZoomFactor = Clamp(ms_ZoomFactor, m_ZoomFactorLimits.x, m_ZoomFactorLimits.y);
}
}
//Check if we have zoom input, using a dead-zone to prevent false hits.
bool camControlHelper::ComputeZoomInput(CControl& control, float& zoomInput) const
{
#if KEYBOARD_MOUSE_SUPPORT
if(control.GetPedSniperZoom().GetSource().m_DeviceIndex == ioSource::IOMD_KEYBOARD_MOUSE)
{
control.SetInputExclusive(INPUT_SNIPER_ZOOM);
}
#endif // KEYBOARD_MOUSE_SUPPORT
const ioValue& zoomControl = control.GetPedSniperZoom();
// Use D-pad to zoom except for cellphone camera.
if( CPauseMenu::GetMenuPreference(PREF_SNIPER_ZOOM) && !CPhoneMgr::CamGetState()
KEYBOARD_MOUSE_ONLY(&& control.GetPedSniperZoom().GetSource().m_DeviceIndex != ioSource::IOMD_KEYBOARD_MOUSE) )
{
zoomInput = 0.0f;
if(control.GetPedSniperZoomInSecondary().IsDown())
{
zoomInput += 1.0f;
}
if(control.GetPedSniperZoomOutSecondary().IsDown())
{
zoomInput -= 1.0f;
}
}
else
{
zoomInput = -zoomControl.GetUnboundNorm();
}
float inputMag = Abs(zoomInput);
bool isInputPresent = (inputMag > 0.0f);
if(isInputPresent)
{
//Apply the custom power factor scaling specified in metadata to change the control response across the range of stick input.
inputMag = rage::Powf(inputMag, m_Metadata.m_Zoom.m_InputMagPowerFactor);
zoomInput = Sign(zoomInput) * inputMag;
}
return isInputPresent;
}
void camControlHelper::UpdateZoomFactorLimits()
{
float minZoomFactor = 1.0f;
float minFov = NetworkInterface::IsGameInProgress() ? m_Metadata.m_Zoom.m_MinFovForNetworkPlay : m_Metadata.m_Zoom.m_MinFov;
minFov = Clamp(minFov, g_MinFov, g_MaxFov);
const float maxFov = Clamp(m_Metadata.m_Zoom.m_MaxFov, g_MinFov, g_MaxFov);
float maxZoomFactor = maxFov / minFov;
maxZoomFactor = Max(maxZoomFactor, minZoomFactor);
if(ms_ShouldOverrideZoomFactorLimits)
{
//Ensure the overridden limits are within our pre-defined limits.
const float overriddenMinZoomFactorToApply = Clamp(ms_OverriddenZoomFactorLimits.x, minZoomFactor, maxZoomFactor);
const float overriddenMaxZoomFactorToApply = Clamp(ms_OverriddenZoomFactorLimits.y, overriddenMinZoomFactorToApply, maxZoomFactor);
m_ZoomFactorLimits.Set(overriddenMinZoomFactorToApply, overriddenMaxZoomFactorToApply);
}
else
{
m_ZoomFactorLimits.Set(minZoomFactor, maxZoomFactor);
}
}
void camControlHelper::UpdateAccurateModeControl(CControl& control)
{
if(m_Metadata.m_ShouldUseAccurateModeInput && !m_ShouldIgnoreAccurateModeInputThisUpdate)
{
// B* 737346: Do not allow accurate mode state to change while camera is interpolating
// as it changes the source camera's frame and mess up the interpolation.
const bool bIsInterpolating = camInterface::GetGameplayDirector().IsCameraInterpolating();
if (!bIsInterpolating)
{
#if KEYBOARD_MOUSE_SUPPORT
// B* 2032581: The way the mouse wheel works we have to always toggle regardless of meta data settings. The reason
// for this is that the wheel cannot be held forwards or backwards. To make this work more like the sniper rifle on
// the mouse, we will use wheel forward for enter accurate aim and wheel backwards for exit accurate aim. On the
// keyboard, this will be two different buttons.
if(control.GetPedAccurateAim().GetSource().m_DeviceIndex == ioSource::IOMD_KEYBOARD_MOUSE)
{
const float aimValue = control.GetPedAccurateAim().GetUnboundNorm();
// Positive is wheel up/forwards.
if(aimValue > ioValue::BUTTON_DOWN_THRESHOLD)
{
m_IsInAccurateMode = false;
control.SetInputExclusive(INPUT_ACCURATE_AIM);
}
else if(aimValue < -ioValue::BUTTON_DOWN_THRESHOLD)
{
m_IsInAccurateMode = true;
control.SetInputExclusive(INPUT_ACCURATE_AIM);
}
}
else
#endif // KEYBOARD_MOUSE_SUPPORT
if(m_Metadata.m_ShouldToggleAccurateModeInput)
{
if(control.GetPedAccurateAim().IsPressed())
{
control.SetInputExclusive(INPUT_ACCURATE_AIM);
m_IsInAccurateMode = !m_IsInAccurateMode;
}
}
else
{
m_IsInAccurateMode = control.GetPedAccurateAim().IsDown();
if(m_IsInAccurateMode)
{
control.SetInputExclusive(INPUT_ACCURATE_AIM);
}
}
}
}
m_ShouldIgnoreAccurateModeInputThisUpdate = false;
}
void camControlHelper::Reset()
{
m_LookAroundSpeed = 0.0f;
m_NormalisedLookAroundHeadingInput = 0.0f;
m_NormalisedLookAroundPitchInput = 0.0f;
m_LookAroundHeadingOffset = 0.0f;
m_LookAroundPitchOffset = 0.0f;
m_LookAroundHeadingInputPreviousUpdate = 0.0f;
if(m_LookAroundInputEnvelope)
{
m_LookAroundInputEnvelope->Stop();
}
m_LookAroundInputEnvelopeLevel = 0.0f;
m_LookAroundDecelerationScaling = 1.0f;
m_ZoomFactorSpeed = 0.0f;
m_TimeReleasedLookBehindInput = 0;
m_LastTimeViewModeControlDisabled = 0;
//Reset the persistent zoom factor (to help with streaming) and check that it is within the valid range for this helper.
ms_ZoomFactor = 1.0f;
UpdateZoomFactorLimits();
ms_ZoomFactor = Clamp(ms_ZoomFactor, m_ZoomFactorLimits.x, m_ZoomFactorLimits.y);
m_ShouldUpdateViewModeThisUpdate = false;
m_ShouldIgnoreViewModeInputThisUpdate = false;
m_ShouldSkipViewModeBlendThisUpdate = false;
m_ShouldIgnoreLookAroundInputThisUpdate = false;
m_IsLookBehindInputActive = true;
m_ShouldIgnoreLookBehindInputThisUpdate = false;
m_IsLookAroundInputPresent = false;
m_IsLookingBehind = false;
m_WasLookingBehind = false;
m_ShouldIgnoreAccurateModeInputThisUpdate = false;
m_IsInAccurateMode = false;
m_IsOverridingViewModeForSpectatorModeThisUpdate = false;
m_ShouldUseOriginalMaxControllerAimSensitivity = false;
m_ShouldForceAimSensitivityThisUpdate = false;
m_ShouldForceDefaultLookAroundSensitivityThisUpdate = false;
m_SkipControlHelperThisUpdate = false;
#if RSG_ORBIS
m_WasPlayerAiming = false;
#endif // RSG_ORBIS
#if RSG_PC
m_WasUsingKeyboardMouseLastInput = false;
#endif // RSG_PC
}
#if __BANK
void camControlHelper::AddWidgets(bkBank& bank)
{
bank.PushGroup("Control helper");
{
bank.AddSlider("Overridden aim sensitivity", &ms_DebugAimSensitivitySetting, -1, g_RevisedMaxControllerAimSensitivity, 1);
bank.AddSlider("Overridden look-around (non-aim) sensitivity", &ms_DebugLookAroundSensitivitySetting, -1, g_RevisedMaxControllerAimSensitivity, 1);
#if RSG_ORBIS
bank.AddSlider("Controller Motion Deadzone", &c_fMotionDeadzone, 0.0f, 45.0f, 1.0f);
bank.AddSlider("Controller Motion vertical input scale", &c_fMotionVerticalScale, 0.0f, 5.0f, 0.5f);
bank.AddSlider("Controller Motion horizontal input scale", &c_fMotionHorizontalScale, 0.0f, 5.0f, 0.5f);
#endif
}
bank.PopGroup(); //Control helper
INSTANTIATE_TUNE_GROUP_INT(FIRST_PERSON_TUNE, LOOK_BEHIND_HELD_TIME, 0, 2000, 50);
}
bool camControlHelper::DebugHadSetViewModeForContext(s32& contextIndex, s32& viewModeForContext)
{
contextIndex = ms_DebugRequestedContextIndex;
viewModeForContext = ms_DebugRequestedViewModeForContext;
return ms_DebugWasViewModeForContextSetThisUpdate;
}
void camControlHelper::DebugResetHadSetViewModeForContext()
{
ms_DebugWasViewModeForContextSetThisUpdate = false;
ms_DebugRequestedContextIndex = -1;
ms_DebugRequestedViewModeForContext = -1;
}
#endif // __BANK