// Copyright Steve Streeting 2022 // Released under the MIT license https://opensource.org/license/MIT/ #pragma once #include "CoreMinimal.h" #include "SUDSValue.h" #include "Subsystems/GameInstanceSubsystem.h" #include "Engine/World.h" #include "Engine/GameInstance.h" #include "SUDSSubsystem.generated.h" class USUDSDialogue; class USUDSScript; class USoundConcurrency; struct FSoundConcurrencySettings; DECLARE_LOG_CATEGORY_EXTERN(LogSUDSSubsystem, Log, All); DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnGlobalVariableChangedEvent, FName, VariableName, const FSUDSValue&, Value, bool, bFromScript); /// Copy of the global state of the system USTRUCT(BlueprintType) struct FSUDSGlobalState { GENERATED_BODY() protected: UPROPERTY(BlueprintReadOnly, SaveGame, Category="SUDS") TMap GlobalVariables; public: FSUDSGlobalState() {} FSUDSGlobalState(const TMap& InGlobalVars) : GlobalVariables(InGlobalVars) { } const TMap& GetGlobalVariables() const { return GlobalVariables; } SUDS_API friend FArchive& operator<<(FArchive& Ar, FSUDSGlobalState& Value); SUDS_API friend void operator<<(FStructuredArchive::FSlot Slot, FSUDSGlobalState& Value); bool Serialize(FStructuredArchive::FSlot Slot) { Slot << *this; return true; } bool Serialize(FArchive& Ar) { Ar << *this; return true; } }; /** * */ UCLASS() class SUDS_API USUDSSubsystem : public UGameInstanceSubsystem { GENERATED_BODY() public: virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Deinitialize() override; /// Event raised when a global variable is changed. "FromScript" is true if the variable was set by the script, false if set from code UPROPERTY(BlueprintAssignable) FOnGlobalVariableChangedEvent OnGlobalVariableChanged; protected: UPROPERTY() TObjectPtr VoiceConcurrency; /// Global variable state TMap GlobalVariableState; void SetGlobalVariableImpl(FName Name, const FSUDSValue& Value, bool bFromScript, int LineNo) { const FSUDSValue OldValue = GetGlobalVariable(Name); if (!IsGlobalVariableSet(Name) || (OldValue != Value).GetBooleanValue()) { GlobalVariableState.Add(Name, Value); OnGlobalVariableChanged.Broadcast(Name, Value, bFromScript); } } public: /** * Sets the number of voiced lines that can be played at once. Defaults to 1, so that when a new voiced line is * played while another is still playing, the previous one is stopped. If you would prefer that they overlap, set * this to >1 * @param ConcurrentLines The number of voice lines that can be played at once. */ UFUNCTION(BlueprintCallable, Category="SUDS|Settings") void SetMaxConcurrentVoicedLines(int ConcurrentLines); /** * Gets the number of voiced lines that can be played at once. Defaults to 1, so that when a new voiced line is * played while another is still playing, the previous one is stopped. */ UFUNCTION(BlueprintCallable, Category="SUDS|Settings") int GetMaxConcurrentVoicedLines() const; USoundConcurrency* GetVoicedLineConcurrency() const { return VoiceConcurrency; } /** * Reset the global state of the system. * @param bResetVariables If true, resets all variable state */ UFUNCTION(BlueprintCallable, Category="SUDS|Global State") void ResetGlobalState(bool bResetVariables = true); /** Retrieve a copy of the global state of the system. * This is useful for saving the state of e.g. global variables. * @return A static copy of the current global state. This struct can be serialised with your save data, * and contains the state of global variables. */ UFUNCTION(BlueprintCallable, Category="SUDS|Global State") FSUDSGlobalState GetSavedGlobalState() const; /** Restore the global saved state. * This is useful for restoring global state eg global variables when you're loading a game. * @param State Global state that you previously retrieved from GetSavedGlobalState(). */ UFUNCTION(BlueprintCallable, Category="SUDS|Dialogue") void RestoreSavedGlobalState(const FSUDSGlobalState& State); /// Set a global variable /// This is mostly only useful if you happen to already have a general purpose FSUDSValue. /// See SetGlobalVariableText, SetGlobalVariableInt etc for literal-friendly versions UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") void SetGlobalVariable(FName Name, FSUDSValue Value) { SetGlobalVariableImpl(Name, Value, false, 0); } /// Internal use only void InternalSetGlobalVariable(FName Name, const FSUDSValue& Value, bool bFromScript, int LineNo) { SetGlobalVariableImpl(Name, Value, bFromScript, LineNo); } /// Get a variable in dialogue state as a general value type /// See GetDialogueText, GetDialogueInt etc for more type friendly versions, but if you want to access the state /// as a type-flexible value then you can do so with this function. UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") FSUDSValue GetGlobalVariable(FName Name) const { if (const auto Arg = GlobalVariableState.Find(Name)) { return *Arg; } return FSUDSValue(); } UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") bool IsGlobalVariableSet(FName Name) const { return GlobalVariableState.Contains(Name); } /// Get all variables UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") const TMap& GetGlobalVariables() const { return GlobalVariableState; } /** * Set a text global variable * @param Name The name of the variable * @param Value The value of the variable */ UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") void SetGlobalVariableText(FName Name, FText Value) { SetGlobalVariable(Name, Value); } /** * Get a text global variable * @param Name The name of the variable * @returns Value The value of the variable */ UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") FText GetGlobalVariableText(FName Name) const; /** * Set a global variable on the passed in parameters collection. * @param Name The name of the variable * @param Value The value of the variable */ UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") void SetGlobalVariableInt(FName Name, int32 Value); /** * Get an int global variable * @param Name The name of the variable * @returns Value The value of the variable */ UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") int GetGlobalVariableInt(FName Name) const; /** * Set a float global variable * @param Name The name of the variable * @param Value The value of the variable */ UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") void SetGlobalVariableFloat(FName Name, float Value); /** * Get a float global variable * @param Name The name of the variable * @returns Value The value of the variable */ UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") float GetGlobalVariableFloat(FName Name) const; /** * Set a gender global variable * @param Name The name of the variable * @param Value The value of the variable */ UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") void SetGlobalVariableGender(FName Name, ETextGender Value); /** * Get a gender global variable * @param Name The name of the variable * @returns Value The value of the variable */ UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") ETextGender GetGlobalVariableGender(FName Name) const; /** * Set a boolean global variable * @param Name The name of the variable * @param Value The value of the variable */ UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") void SetGlobalVariableBoolean(FName Name, bool Value); /** * Get a boolean global variable * @param Name The name of the variable * @returns Value The value of the variable */ UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") bool GetGlobalVariableBoolean(FName Name) const; /** * Set a name global variable * @param Name The name of the variable * @param Value The value of the variable */ UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") void SetGlobalVariableName(FName Name, FName Value); /** * Get a name global variable * @param Name The name of the variable * @returns Value The value of the variable */ UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") FName GetGlobalVariableName(FName Name) const; /** * Remove the definition of a global variable. * This has much same effect as setting the variable back to the default value for this type, since attempting to * retrieve a missing variable result in a default value. * @param Name The name of the variable */ UFUNCTION(BlueprintCallable, Category="SUDS|Global Variables") void UnSetGlobalVariable(FName Name); #if WITH_EDITORONLY_DATA /// Only for use by tests / editor tools when real subsystem isn't running static TMap Test_DummyGlobalVariables; #endif }; inline USUDSSubsystem* GetSUDSSubsystem(UWorld* WorldContext) { if (IsValid(WorldContext) && WorldContext->IsGameWorld()) { auto GI = WorldContext->GetGameInstance(); if (IsValid(GI)) return GI->GetSubsystem(); } return nullptr; }