372 lines
12 KiB
C
372 lines
12 KiB
C
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||
|
|
||
|
#pragma once
|
||
|
|
||
|
#include "CoreMinimal.h"
|
||
|
#include "SubtitleDisplaySubsystem.h"
|
||
|
#include "GameFramework/SaveGame.h"
|
||
|
#include "LyraSettingsShared.generated.h"
|
||
|
|
||
|
UENUM(BlueprintType)
|
||
|
enum class EColorBlindMode : uint8
|
||
|
{
|
||
|
Off,
|
||
|
// Deuteranope (green weak/blind)
|
||
|
Deuteranope,
|
||
|
// Protanope (red weak/blind)
|
||
|
Protanope,
|
||
|
// Tritanope(blue weak / bind)
|
||
|
Tritanope
|
||
|
};
|
||
|
|
||
|
UENUM(BlueprintType)
|
||
|
enum class ELyraAllowBackgroundAudioSetting : uint8
|
||
|
{
|
||
|
Off,
|
||
|
AllSounds,
|
||
|
|
||
|
Num UMETA(Hidden),
|
||
|
};
|
||
|
|
||
|
UENUM(BlueprintType)
|
||
|
enum class ELyraGamepadSensitivity : uint8
|
||
|
{
|
||
|
Invalid = 0 UMETA(Hidden),
|
||
|
|
||
|
Slow UMETA(DisplayName = "01 - Slow"),
|
||
|
SlowPlus UMETA(DisplayName = "02 - Slow+"),
|
||
|
SlowPlusPlus UMETA(DisplayName = "03 - Slow++"),
|
||
|
Normal UMETA(DisplayName = "04 - Normal"),
|
||
|
NormalPlus UMETA(DisplayName = "05 - Normal+"),
|
||
|
NormalPlusPlus UMETA(DisplayName = "06 - Normal++"),
|
||
|
Fast UMETA(DisplayName = "07 - Fast"),
|
||
|
FastPlus UMETA(DisplayName = "08 - Fast+"),
|
||
|
FastPlusPlus UMETA(DisplayName = "09 - Fast++"),
|
||
|
Insane UMETA(DisplayName = "10 - Insane"),
|
||
|
|
||
|
MAX UMETA(Hidden),
|
||
|
};
|
||
|
|
||
|
class ULyraLocalPlayer;
|
||
|
|
||
|
/**
|
||
|
* ULyraSettingsShared - The "Shared" settings are stored as part of the USaveGame system, these settings are not machine
|
||
|
* specific like the local settings, and are safe to store in the cloud - and 'share' them. Using the save game system
|
||
|
* we can also store settings per player, so things like controller keybind preferences should go here, because if those
|
||
|
* are stored in the local settings all users would get them.
|
||
|
*
|
||
|
* TODO NDarnell Future version rename this maybe to CloudSave? Even though these arent necessarily in the cloud...
|
||
|
* maybe change Localsettings to LyraPlatformSettings, or DeviceSettings? Make this one UserSettings? TBD
|
||
|
*
|
||
|
* NOTE: I want to do Within=LocalPlayer, but SaveGames create the object in the transient package, instead
|
||
|
* of getting to select the outer, maybe LoadGameFromMemory should have a variant, like LoadGameFromMemory_WithOuter, or maybe pass in
|
||
|
* an optional outer.
|
||
|
*/
|
||
|
UCLASS(/*Within=LocalPlayer*/)
|
||
|
class ULyraSettingsShared : public USaveGame
|
||
|
{
|
||
|
GENERATED_BODY()
|
||
|
|
||
|
public:
|
||
|
DECLARE_EVENT_OneParam(ULyraSettingsShared, FOnSettingChangedEvent, ULyraSettingsShared* Settings);
|
||
|
FOnSettingChangedEvent OnSettingChanged;
|
||
|
|
||
|
public:
|
||
|
|
||
|
ULyraSettingsShared();
|
||
|
|
||
|
void Initialize(ULyraLocalPlayer* LocalPlayer);
|
||
|
|
||
|
bool IsDirty() const { return bIsDirty; }
|
||
|
void ClearDirtyFlag() { bIsDirty = false; }
|
||
|
|
||
|
void SaveSettings();
|
||
|
static ULyraSettingsShared* LoadOrCreateSettings(const ULyraLocalPlayer* LocalPlayer);
|
||
|
|
||
|
void ApplySettings();
|
||
|
|
||
|
public:
|
||
|
////////////////////////////////////////////////////////
|
||
|
// Color Blind Options
|
||
|
|
||
|
UFUNCTION()
|
||
|
EColorBlindMode GetColorBlindMode() const;
|
||
|
UFUNCTION()
|
||
|
void SetColorBlindMode(EColorBlindMode InMode);
|
||
|
|
||
|
UFUNCTION()
|
||
|
int32 GetColorBlindStrength() const;
|
||
|
UFUNCTION()
|
||
|
void SetColorBlindStrength(int32 InColorBlindStrength);
|
||
|
|
||
|
private:
|
||
|
UPROPERTY()
|
||
|
EColorBlindMode ColorBlindMode = EColorBlindMode::Off;
|
||
|
|
||
|
UPROPERTY()
|
||
|
int32 ColorBlindStrength = 10;
|
||
|
|
||
|
////////////////////////////////////////////////////////
|
||
|
// Gamepad Vibration
|
||
|
public:
|
||
|
UFUNCTION()
|
||
|
bool GetForceFeedbackEnabled() const { return bForceFeedbackEnabled; }
|
||
|
|
||
|
UFUNCTION()
|
||
|
void SetForceFeedbackEnabled(const bool NewValue) { ChangeValueAndDirty(bForceFeedbackEnabled, NewValue); }
|
||
|
|
||
|
private:
|
||
|
/** Is force feedback enabled when a controller is being used? */
|
||
|
UPROPERTY()
|
||
|
bool bForceFeedbackEnabled = true;
|
||
|
|
||
|
////////////////////////////////////////////////////////
|
||
|
// Gamepad Deadzone
|
||
|
public:
|
||
|
/** Getter for gamepad move stick dead zone value. */
|
||
|
UFUNCTION()
|
||
|
float GetGamepadMoveStickDeadZone() const { return GamepadMoveStickDeadZone; }
|
||
|
|
||
|
/** Setter for gamepad move stick dead zone value. */
|
||
|
UFUNCTION()
|
||
|
void SetGamepadMoveStickDeadZone(const float NewValue) { ChangeValueAndDirty(GamepadMoveStickDeadZone, NewValue); }
|
||
|
|
||
|
/** Getter for gamepad look stick dead zone value. */
|
||
|
UFUNCTION()
|
||
|
float GetGamepadLookStickDeadZone() const { return GamepadLookStickDeadZone; }
|
||
|
|
||
|
/** Setter for gamepad look stick dead zone value. */
|
||
|
UFUNCTION()
|
||
|
void SetGamepadLookStickDeadZone(const float NewValue) { ChangeValueAndDirty(GamepadLookStickDeadZone, NewValue); }
|
||
|
|
||
|
private:
|
||
|
/** Holds the gamepad move stick dead zone value. */
|
||
|
UPROPERTY()
|
||
|
float GamepadMoveStickDeadZone;
|
||
|
|
||
|
/** Holds the gamepad look stick dead zone value. */
|
||
|
UPROPERTY()
|
||
|
float GamepadLookStickDeadZone;
|
||
|
|
||
|
////////////////////////////////////////////////////////
|
||
|
// Gamepad Trigger Haptics
|
||
|
public:
|
||
|
UFUNCTION()
|
||
|
bool GetTriggerHapticsEnabled() const { return bTriggerHapticsEnabled; }
|
||
|
UFUNCTION()
|
||
|
void SetTriggerHapticsEnabled(const bool NewValue) { ChangeValueAndDirty(bTriggerHapticsEnabled, NewValue); }
|
||
|
|
||
|
UFUNCTION()
|
||
|
bool GetTriggerPullUsesHapticThreshold() const { return bTriggerPullUsesHapticThreshold; }
|
||
|
UFUNCTION()
|
||
|
void SetTriggerPullUsesHapticThreshold(const bool NewValue) { ChangeValueAndDirty(bTriggerPullUsesHapticThreshold, NewValue); }
|
||
|
|
||
|
UFUNCTION()
|
||
|
uint8 GetTriggerHapticStrength() const { return TriggerHapticStrength; }
|
||
|
UFUNCTION()
|
||
|
void SetTriggerHapticStrength(const uint8 NewValue) { ChangeValueAndDirty(TriggerHapticStrength, NewValue); }
|
||
|
|
||
|
UFUNCTION()
|
||
|
uint8 GetTriggerHapticStartPosition() const { return TriggerHapticStartPosition; }
|
||
|
UFUNCTION()
|
||
|
void SetTriggerHapticStartPosition(const uint8 NewValue) { ChangeValueAndDirty(TriggerHapticStartPosition, NewValue); }
|
||
|
|
||
|
private:
|
||
|
/** Are trigger haptics enabled? */
|
||
|
UPROPERTY()
|
||
|
bool bTriggerHapticsEnabled = false;
|
||
|
/** Does the game use the haptic feedback as its threshold for judging button presses? */
|
||
|
UPROPERTY()
|
||
|
bool bTriggerPullUsesHapticThreshold = true;
|
||
|
/** The strength of the trigger haptic effects. */
|
||
|
UPROPERTY()
|
||
|
uint8 TriggerHapticStrength = 8;
|
||
|
/** The start position of the trigger haptic effects */
|
||
|
UPROPERTY()
|
||
|
uint8 TriggerHapticStartPosition = 0;
|
||
|
|
||
|
////////////////////////////////////////////////////////
|
||
|
// Subtitles
|
||
|
public:
|
||
|
UFUNCTION()
|
||
|
bool GetSubtitlesEnabled() const { return bEnableSubtitles; }
|
||
|
UFUNCTION()
|
||
|
void SetSubtitlesEnabled(bool Value) { ChangeValueAndDirty(bEnableSubtitles, Value); }
|
||
|
|
||
|
UFUNCTION()
|
||
|
ESubtitleDisplayTextSize GetSubtitlesTextSize() const { return SubtitleTextSize; }
|
||
|
UFUNCTION()
|
||
|
void SetSubtitlesTextSize(ESubtitleDisplayTextSize Value) { ChangeValueAndDirty(SubtitleTextSize, Value); ApplySubtitleOptions(); }
|
||
|
|
||
|
UFUNCTION()
|
||
|
ESubtitleDisplayTextColor GetSubtitlesTextColor() const { return SubtitleTextColor; }
|
||
|
UFUNCTION()
|
||
|
void SetSubtitlesTextColor(ESubtitleDisplayTextColor Value) { ChangeValueAndDirty(SubtitleTextColor, Value); ApplySubtitleOptions(); }
|
||
|
|
||
|
UFUNCTION()
|
||
|
ESubtitleDisplayTextBorder GetSubtitlesTextBorder() const { return SubtitleTextBorder; }
|
||
|
UFUNCTION()
|
||
|
void SetSubtitlesTextBorder(ESubtitleDisplayTextBorder Value) { ChangeValueAndDirty(SubtitleTextBorder, Value); ApplySubtitleOptions(); }
|
||
|
|
||
|
UFUNCTION()
|
||
|
ESubtitleDisplayBackgroundOpacity GetSubtitlesBackgroundOpacity() const { return SubtitleBackgroundOpacity; }
|
||
|
UFUNCTION()
|
||
|
void SetSubtitlesBackgroundOpacity(ESubtitleDisplayBackgroundOpacity Value) { ChangeValueAndDirty(SubtitleBackgroundOpacity, Value); ApplySubtitleOptions(); }
|
||
|
|
||
|
void ApplySubtitleOptions();
|
||
|
|
||
|
private:
|
||
|
UPROPERTY()
|
||
|
bool bEnableSubtitles = true;
|
||
|
|
||
|
UPROPERTY()
|
||
|
ESubtitleDisplayTextSize SubtitleTextSize = ESubtitleDisplayTextSize::Medium;
|
||
|
|
||
|
UPROPERTY()
|
||
|
ESubtitleDisplayTextColor SubtitleTextColor = ESubtitleDisplayTextColor::White;
|
||
|
|
||
|
UPROPERTY()
|
||
|
ESubtitleDisplayTextBorder SubtitleTextBorder = ESubtitleDisplayTextBorder::None;
|
||
|
|
||
|
UPROPERTY()
|
||
|
ESubtitleDisplayBackgroundOpacity SubtitleBackgroundOpacity = ESubtitleDisplayBackgroundOpacity::Medium;
|
||
|
|
||
|
////////////////////////////////////////////////////////
|
||
|
// Shared audio settings
|
||
|
public:
|
||
|
UFUNCTION()
|
||
|
ELyraAllowBackgroundAudioSetting GetAllowAudioInBackgroundSetting() const { return AllowAudioInBackground; }
|
||
|
UFUNCTION()
|
||
|
void SetAllowAudioInBackgroundSetting(ELyraAllowBackgroundAudioSetting NewValue);
|
||
|
|
||
|
void ApplyBackgroundAudioSettings();
|
||
|
|
||
|
private:
|
||
|
UPROPERTY()
|
||
|
ELyraAllowBackgroundAudioSetting AllowAudioInBackground = ELyraAllowBackgroundAudioSetting::Off;
|
||
|
|
||
|
////////////////////////////////////////////////////////
|
||
|
// Culture / language
|
||
|
public:
|
||
|
/** Gets the pending culture */
|
||
|
const FString& GetPendingCulture() const;
|
||
|
|
||
|
/** Sets the pending culture to apply */
|
||
|
void SetPendingCulture(const FString& NewCulture);
|
||
|
|
||
|
// Called when the culture changes.
|
||
|
void OnCultureChanged();
|
||
|
|
||
|
/** Clears the pending culture to apply */
|
||
|
void ClearPendingCulture();
|
||
|
|
||
|
bool IsUsingDefaultCulture() const;
|
||
|
|
||
|
void ResetToDefaultCulture();
|
||
|
bool ShouldResetToDefaultCulture() const { return bResetToDefaultCulture; }
|
||
|
|
||
|
void ApplyCultureSettings();
|
||
|
void ResetCultureToCurrentSettings();
|
||
|
|
||
|
private:
|
||
|
/** The pending culture to apply */
|
||
|
UPROPERTY(Transient)
|
||
|
FString PendingCulture;
|
||
|
|
||
|
/* If true, resets the culture to default. */
|
||
|
bool bResetToDefaultCulture = false;
|
||
|
|
||
|
////////////////////////////////////////////////////////
|
||
|
// Gamepad Sensitivity
|
||
|
public:
|
||
|
UFUNCTION()
|
||
|
double GetMouseSensitivityX() const { return MouseSensitivityX; }
|
||
|
UFUNCTION()
|
||
|
void SetMouseSensitivityX(double NewValue) { ChangeValueAndDirty(MouseSensitivityX, NewValue); ApplyInputSensitivity(); }
|
||
|
|
||
|
UFUNCTION()
|
||
|
double GetMouseSensitivityY() const { return MouseSensitivityY; }
|
||
|
UFUNCTION()
|
||
|
void SetMouseSensitivityY(double NewValue) { ChangeValueAndDirty(MouseSensitivityY, NewValue); ApplyInputSensitivity(); }
|
||
|
|
||
|
UFUNCTION()
|
||
|
double GetTargetingMultiplier() const { return TargetingMultiplier; }
|
||
|
UFUNCTION()
|
||
|
void SetTargetingMultiplier(double NewValue) { ChangeValueAndDirty(TargetingMultiplier, NewValue); ApplyInputSensitivity(); }
|
||
|
|
||
|
UFUNCTION()
|
||
|
bool GetInvertVerticalAxis() const { return bInvertVerticalAxis; }
|
||
|
UFUNCTION()
|
||
|
void SetInvertVerticalAxis(bool NewValue) { ChangeValueAndDirty(bInvertVerticalAxis, NewValue); ApplyInputSensitivity(); }
|
||
|
|
||
|
UFUNCTION()
|
||
|
bool GetInvertHorizontalAxis() const { return bInvertHorizontalAxis; }
|
||
|
UFUNCTION()
|
||
|
void SetInvertHorizontalAxis(bool NewValue) { ChangeValueAndDirty(bInvertHorizontalAxis, NewValue); ApplyInputSensitivity(); }
|
||
|
|
||
|
private:
|
||
|
/** Holds the mouse horizontal sensitivity */
|
||
|
UPROPERTY()
|
||
|
double MouseSensitivityX = 1.0;
|
||
|
|
||
|
/** Holds the mouse vertical sensitivity */
|
||
|
UPROPERTY()
|
||
|
double MouseSensitivityY = 1.0;
|
||
|
|
||
|
/** Multiplier applied while Aiming down sights. */
|
||
|
UPROPERTY()
|
||
|
double TargetingMultiplier = 0.5;
|
||
|
|
||
|
/** If true then the vertical look axis should be inverted */
|
||
|
UPROPERTY()
|
||
|
bool bInvertVerticalAxis = false;
|
||
|
|
||
|
/** If true then the horizontal look axis should be inverted */
|
||
|
UPROPERTY()
|
||
|
bool bInvertHorizontalAxis = false;
|
||
|
|
||
|
////////////////////////////////////////////////////////
|
||
|
// Gamepad Sensitivity
|
||
|
public:
|
||
|
UFUNCTION()
|
||
|
ELyraGamepadSensitivity GetGamepadLookSensitivityPreset() const { return GamepadLookSensitivityPreset; }
|
||
|
UFUNCTION()
|
||
|
void SetLookSensitivityPreset(ELyraGamepadSensitivity NewValue) { ChangeValueAndDirty(GamepadLookSensitivityPreset, NewValue); ApplyInputSensitivity(); }
|
||
|
|
||
|
UFUNCTION()
|
||
|
ELyraGamepadSensitivity GetGamepadTargetingSensitivityPreset() const { return GamepadTargetingSensitivityPreset; }
|
||
|
UFUNCTION()
|
||
|
void SetGamepadTargetingSensitivityPreset(ELyraGamepadSensitivity NewValue) { ChangeValueAndDirty(GamepadTargetingSensitivityPreset, NewValue); ApplyInputSensitivity(); }
|
||
|
|
||
|
void ApplyInputSensitivity();
|
||
|
|
||
|
private:
|
||
|
UPROPERTY()
|
||
|
ELyraGamepadSensitivity GamepadLookSensitivityPreset = ELyraGamepadSensitivity::Normal;
|
||
|
UPROPERTY()
|
||
|
ELyraGamepadSensitivity GamepadTargetingSensitivityPreset = ELyraGamepadSensitivity::Normal;
|
||
|
|
||
|
////////////////////////////////////////////////////////
|
||
|
/// Dirty and Change Reporting
|
||
|
private:
|
||
|
template<typename T>
|
||
|
bool ChangeValueAndDirty(T& CurrentValue, const T& NewValue)
|
||
|
{
|
||
|
if (CurrentValue != NewValue)
|
||
|
{
|
||
|
CurrentValue = NewValue;
|
||
|
bIsDirty = true;
|
||
|
OnSettingChanged.Broadcast(this);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool bIsDirty = false;
|
||
|
|
||
|
UPROPERTY(Transient)
|
||
|
ULyraLocalPlayer* OwningPlayer = nullptr;
|
||
|
};
|