// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "Subsystems/WorldSubsystem.h" #include "GameplayTagContainer.h" #include "GameplayAbilitySpec.h" #include "LyraGamePhaseSubsystem.generated.h" class ULyraGamePhaseAbility; DECLARE_DYNAMIC_DELEGATE_OneParam(FLyraGamePhaseDynamicDelegate, const ULyraGamePhaseAbility*, Phase); DECLARE_DELEGATE_OneParam(FLyraGamePhaseDelegate, const ULyraGamePhaseAbility* Phase); DECLARE_DYNAMIC_DELEGATE_OneParam(FLyraGamePhaseTagDynamicDelegate, const FGameplayTag&, PhaseTag); DECLARE_DELEGATE_OneParam(FLyraGamePhaseTagDelegate, const FGameplayTag& PhaseTag); // Match rule for message receivers UENUM(BlueprintType) enum class EPhaseTagMatchType : uint8 { // An exact match will only receive messages with exactly the same channel // (e.g., registering for "A.B" will match a broadcast of A.B but not A.B.C) ExactMatch, // A partial match will receive any messages rooted in the same channel // (e.g., registering for "A.B" will match a broadcast of A.B as well as A.B.C) PartialMatch }; /** */ UCLASS() class ULyraGamePhaseSubsystem : public UWorldSubsystem { GENERATED_BODY() public: ULyraGamePhaseSubsystem(); virtual void PostInitialize() override; virtual bool ShouldCreateSubsystem(UObject* Outer) const override; void StartPhase(TSubclassOf PhaseAbility, FLyraGamePhaseDelegate PhaseEndedCallback = FLyraGamePhaseDelegate()); //TODO Return a handle so folks can delete these. They will just grow until the world resets. //TODO Should we just occasionally clean these observers up? It's not as if everyone will properly unhook them even if there is a handle. void WhenPhaseStartsOrIsActive(FGameplayTag PhaseTag, EPhaseTagMatchType MatchType, const FLyraGamePhaseTagDelegate& WhenPhaseActive); void WhenPhaseEnds(FGameplayTag PhaseTag, EPhaseTagMatchType MatchType, const FLyraGamePhaseTagDelegate& WhenPhaseEnd); UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, BlueprintPure = false, meta = (AutoCreateRefTerm = "PhaseTag")) bool IsPhaseActive(const FGameplayTag& PhaseTag) const; protected: virtual bool DoesSupportWorldType(const EWorldType::Type WorldType) const override; UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Game Phase", meta = (DisplayName="Start Phase", AutoCreateRefTerm = "PhaseEnded")) void K2_StartPhase(TSubclassOf Phase, const FLyraGamePhaseDynamicDelegate& PhaseEnded); UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Game Phase", meta = (DisplayName = "When Phase Starts or Is Active", AutoCreateRefTerm = "WhenPhaseActive")) void K2_WhenPhaseStartsOrIsActive(FGameplayTag PhaseTag, EPhaseTagMatchType MatchType, FLyraGamePhaseTagDynamicDelegate WhenPhaseActive); UFUNCTION(BlueprintCallable, BlueprintAuthorityOnly, Category = "Game Phase", meta = (DisplayName = "When Phase Ends", AutoCreateRefTerm = "WhenPhaseEnd")) void K2_WhenPhaseEnds(FGameplayTag PhaseTag, EPhaseTagMatchType MatchType, FLyraGamePhaseTagDynamicDelegate WhenPhaseEnd); void OnBeginPhase(const ULyraGamePhaseAbility* PhaseAbility, const FGameplayAbilitySpecHandle PhaseAbilityHandle); void OnEndPhase(const ULyraGamePhaseAbility* PhaseAbility, const FGameplayAbilitySpecHandle PhaseAbilityHandle); private: struct FLyraGamePhaseEntry { public: FGameplayTag PhaseTag; FLyraGamePhaseDelegate PhaseEndedCallback; }; TMap ActivePhaseMap; struct FPhaseObserver { public: bool IsMatch(const FGameplayTag& ComparePhaseTag) const; FGameplayTag PhaseTag; EPhaseTagMatchType MatchType = EPhaseTagMatchType::ExactMatch; FLyraGamePhaseTagDelegate PhaseCallback; }; TArray PhaseStartObservers; TArray PhaseEndObservers; friend class ULyraGamePhaseAbility; };