// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraGamePhaseSubsystem.h" #include "LyraGamePhaseAbility.h" #include "GameplayTagsManager.h" #include "GameFramework/GameState.h" #include "AbilitySystemComponent.h" #include "AbilitySystem/Abilities/LyraGameplayAbility.h" #include "AbilitySystem/LyraAbilitySystemComponent.h" #include "LyraGamePhaseLog.h" DEFINE_LOG_CATEGORY(LogLyraGamePhase); ////////////////////////////////////////////////////////////////////// // ULyraGamePhaseSubsystem ULyraGamePhaseSubsystem::ULyraGamePhaseSubsystem() { } void ULyraGamePhaseSubsystem::PostInitialize() { Super::PostInitialize(); } bool ULyraGamePhaseSubsystem::ShouldCreateSubsystem(UObject* Outer) const { if (Super::ShouldCreateSubsystem(Outer)) { //UWorld* World = Cast(Outer); //check(World); //return World->GetAuthGameMode() != nullptr; //return nullptr; return true; } return false; } bool ULyraGamePhaseSubsystem::DoesSupportWorldType(const EWorldType::Type WorldType) const { return WorldType == EWorldType::Game || WorldType == EWorldType::PIE; } void ULyraGamePhaseSubsystem::StartPhase(TSubclassOf PhaseAbility, FLyraGamePhaseDelegate PhaseEndedCallback) { UWorld* World = GetWorld(); ULyraAbilitySystemComponent* GameState_ASC = World->GetGameState()->FindComponentByClass(); if (ensure(GameState_ASC)) { FGameplayAbilitySpec PhaseSpec(PhaseAbility, 1, 0, this); FGameplayAbilitySpecHandle SpecHandle = GameState_ASC->GiveAbilityAndActivateOnce(PhaseSpec); FGameplayAbilitySpec* FoundSpec = GameState_ASC->FindAbilitySpecFromHandle(SpecHandle); if (FoundSpec && FoundSpec->IsActive()) { FLyraGamePhaseEntry& Entry = ActivePhaseMap.FindOrAdd(SpecHandle); Entry.PhaseEndedCallback = PhaseEndedCallback; } else { PhaseEndedCallback.ExecuteIfBound(nullptr); } } } void ULyraGamePhaseSubsystem::K2_StartPhase(TSubclassOf PhaseAbility, const FLyraGamePhaseDynamicDelegate& PhaseEndedDelegate) { const FLyraGamePhaseDelegate EndedDelegate = FLyraGamePhaseDelegate::CreateWeakLambda(const_cast(PhaseEndedDelegate.GetUObject()), [PhaseEndedDelegate](const ULyraGamePhaseAbility* PhaseAbility) { PhaseEndedDelegate.ExecuteIfBound(PhaseAbility); }); StartPhase(PhaseAbility, EndedDelegate); } void ULyraGamePhaseSubsystem::K2_WhenPhaseStartsOrIsActive(FGameplayTag PhaseTag, EPhaseTagMatchType MatchType, FLyraGamePhaseTagDynamicDelegate WhenPhaseActive) { const FLyraGamePhaseTagDelegate ActiveDelegate = FLyraGamePhaseTagDelegate::CreateWeakLambda(WhenPhaseActive.GetUObject(), [WhenPhaseActive](const FGameplayTag& PhaseTag) { WhenPhaseActive.ExecuteIfBound(PhaseTag); }); WhenPhaseStartsOrIsActive(PhaseTag, MatchType, ActiveDelegate); } void ULyraGamePhaseSubsystem::K2_WhenPhaseEnds(FGameplayTag PhaseTag, EPhaseTagMatchType MatchType, FLyraGamePhaseTagDynamicDelegate WhenPhaseEnd) { const FLyraGamePhaseTagDelegate EndedDelegate = FLyraGamePhaseTagDelegate::CreateWeakLambda(WhenPhaseEnd.GetUObject(), [WhenPhaseEnd](const FGameplayTag& PhaseTag) { WhenPhaseEnd.ExecuteIfBound(PhaseTag); }); WhenPhaseEnds(PhaseTag, MatchType, EndedDelegate); } void ULyraGamePhaseSubsystem::WhenPhaseStartsOrIsActive(FGameplayTag PhaseTag, EPhaseTagMatchType MatchType, const FLyraGamePhaseTagDelegate& WhenPhaseActive) { FPhaseObserver Observer; Observer.PhaseTag = PhaseTag; Observer.MatchType = MatchType; Observer.PhaseCallback = WhenPhaseActive; PhaseStartObservers.Add(Observer); if (IsPhaseActive(PhaseTag)) { WhenPhaseActive.ExecuteIfBound(PhaseTag); } } void ULyraGamePhaseSubsystem::WhenPhaseEnds(FGameplayTag PhaseTag, EPhaseTagMatchType MatchType, const FLyraGamePhaseTagDelegate& WhenPhaseEnd) { FPhaseObserver Observer; Observer.PhaseTag = PhaseTag; Observer.MatchType = MatchType; Observer.PhaseCallback = WhenPhaseEnd; PhaseEndObservers.Add(Observer); } bool ULyraGamePhaseSubsystem::IsPhaseActive(const FGameplayTag& PhaseTag) const { for (const auto& KVP : ActivePhaseMap) { const FLyraGamePhaseEntry& PhaseEntry = KVP.Value; if (PhaseEntry.PhaseTag.MatchesTag(PhaseTag)) { return true; } } return false; } void ULyraGamePhaseSubsystem::OnBeginPhase(const ULyraGamePhaseAbility* PhaseAbility, const FGameplayAbilitySpecHandle PhaseAbilityHandle) { const FGameplayTag IncomingPhaseTag = PhaseAbility->GetGamePhaseTag(); const FGameplayTag IncomingPhaseParentTag = UGameplayTagsManager::Get().RequestGameplayTagDirectParent(IncomingPhaseTag); UE_LOG(LogLyraGamePhase, Log, TEXT("Beginning Phase '%s' (%s)"), *IncomingPhaseTag.ToString(), *GetNameSafe(PhaseAbility)); const UWorld* World = GetWorld(); ULyraAbilitySystemComponent* GameState_ASC = World->GetGameState()->FindComponentByClass(); if (ensure(GameState_ASC)) { TArray ActivePhases; for (const auto& KVP : ActivePhaseMap) { const FGameplayAbilitySpecHandle ActiveAbilityHandle = KVP.Key; if (FGameplayAbilitySpec* Spec = GameState_ASC->FindAbilitySpecFromHandle(ActiveAbilityHandle)) { ActivePhases.Add(Spec); } } for (const FGameplayAbilitySpec* ActivePhase : ActivePhases) { const ULyraGamePhaseAbility* ActivePhaseAbility = CastChecked(ActivePhase->Ability); const FGameplayTag ActivePhaseTag = ActivePhaseAbility->GetGamePhaseTag(); // So if the active phase currently matches the incoming phase tag, we allow it. // i.e. multiple gameplay abilities can all be associated with the same phase tag. // For example, // You can be in the, Game.Playing, phase, and then start a sub-phase, like Game.Playing.SuddenDeath // Game.Playing phase will still be active, and if someone were to push another one, like, // Game.Playing.ActualSuddenDeath, it would end Game.Playing.SuddenDeath phase, but Game.Playing would // continue. Similarly if we activated Game.GameOver, all the Game.Playing* phases would end. if (!ActivePhaseTag.MatchesTag(IncomingPhaseTag) && ActivePhaseTag.MatchesTag(IncomingPhaseParentTag)) { UE_LOG(LogLyraGamePhase, Log, TEXT("\tEnding Phase '%s' (%s)"), *ActivePhaseTag.ToString(), *GetNameSafe(ActivePhaseAbility)); FGameplayAbilitySpecHandle HandleToEnd = ActivePhase->Handle; GameState_ASC->CancelAbilitiesByFunc([HandleToEnd](const ULyraGameplayAbility* LyraAbility, FGameplayAbilitySpecHandle Handle) { return Handle == HandleToEnd; }, true); } } FLyraGamePhaseEntry& Entry = ActivePhaseMap.FindOrAdd(PhaseAbilityHandle); Entry.PhaseTag = IncomingPhaseTag; // Notify all observers of this phase that it has started. for (const FPhaseObserver& Observer : PhaseStartObservers) { if (Observer.IsMatch(IncomingPhaseTag)) { Observer.PhaseCallback.ExecuteIfBound(IncomingPhaseTag); } } } } void ULyraGamePhaseSubsystem::OnEndPhase(const ULyraGamePhaseAbility* PhaseAbility, const FGameplayAbilitySpecHandle PhaseAbilityHandle) { const FGameplayTag EndedPhaseTag = PhaseAbility->GetGamePhaseTag(); UE_LOG(LogLyraGamePhase, Log, TEXT("Ended Phase '%s' (%s)"), *EndedPhaseTag.ToString(), *GetNameSafe(PhaseAbility)); const FLyraGamePhaseEntry& Entry = ActivePhaseMap.FindChecked(PhaseAbilityHandle); Entry.PhaseEndedCallback.ExecuteIfBound(PhaseAbility); ActivePhaseMap.Remove(PhaseAbilityHandle); // Notify all observers of this phase that it has ended. for (const FPhaseObserver& Observer : PhaseEndObservers) { if (Observer.IsMatch(EndedPhaseTag)) { Observer.PhaseCallback.ExecuteIfBound(EndedPhaseTag); } } } bool ULyraGamePhaseSubsystem::FPhaseObserver::IsMatch(const FGameplayTag& ComparePhaseTag) const { switch(MatchType) { case EPhaseTagMatchType::ExactMatch: return ComparePhaseTag == PhaseTag; case EPhaseTagMatchType::PartialMatch: return ComparePhaseTag.MatchesTag(PhaseTag); } return false; }