// Copyright Epic Games, Inc. All Rights Reserved. #include "Teams/LyraTeamSubsystem.h" #include "AbilitySystemGlobals.h" #include "Containers/UnrealString.h" #include "CoreTypes.h" #include "GameFramework/Actor.h" #include "GameFramework/CheatManager.h" #include "GameFramework/Controller.h" #include "GameFramework/Pawn.h" #include "GameplayTagContainer.h" #include "GenericTeamAgentInterface.h" #include "Logging/LogCategory.h" #include "Logging/LogMacros.h" #include "LyraLogChannels.h" #include "LyraTeamAgentInterface.h" #include "LyraTeamCheats.h" #include "LyraTeamPrivateInfo.h" #include "LyraTeamPublicInfo.h" #include "Misc/AssertionMacros.h" #include "Player/LyraPlayerState.h" #include "System/GameplayTagStack.h" #include "Teams/LyraTeamInfoBase.h" #include "Templates/Casts.h" #include "Templates/Tuple.h" #include "Trace/Detail/Channel.h" #include "UObject/UObjectBaseUtility.h" class FSubsystemCollectionBase; ////////////////////////////////////////////////////////////////////// // FLyraTeamTrackingInfo void FLyraTeamTrackingInfo::SetTeamInfo(ALyraTeamInfoBase* Info) { if (ALyraTeamPublicInfo* NewPublicInfo = Cast(Info)) { ensure((PublicInfo == nullptr) || (PublicInfo == NewPublicInfo)); PublicInfo = NewPublicInfo; ULyraTeamDisplayAsset* OldDisplayAsset = DisplayAsset; DisplayAsset = NewPublicInfo->GetTeamDisplayAsset(); if (OldDisplayAsset != DisplayAsset) { OnTeamDisplayAssetChanged.Broadcast(DisplayAsset); } } else if (ALyraTeamPrivateInfo* NewPrivateInfo = Cast(Info)) { ensure((PrivateInfo == nullptr) || (PrivateInfo == NewPrivateInfo)); PrivateInfo = NewPrivateInfo; } else { checkf(false, TEXT("Expected a public or private team info but got %s"), *GetPathNameSafe(Info)) } } void FLyraTeamTrackingInfo::RemoveTeamInfo(ALyraTeamInfoBase* Info) { if (PublicInfo == Info) { PublicInfo = nullptr; } else if (PrivateInfo == Info) { PrivateInfo = nullptr; } else { ensureMsgf(false, TEXT("Expected a previously registered team info but got %s"), *GetPathNameSafe(Info)); } } ////////////////////////////////////////////////////////////////////// // ULyraTeamSubsystem ULyraTeamSubsystem::ULyraTeamSubsystem() { } void ULyraTeamSubsystem::Initialize(FSubsystemCollectionBase& Collection) { Super::Initialize(Collection); auto AddTeamCheats = [](UCheatManager* CheatManager) { CheatManager->AddCheatManagerExtension(NewObject(CheatManager)); }; CheatManagerRegistrationHandle = UCheatManager::RegisterForOnCheatManagerCreated(FOnCheatManagerCreated::FDelegate::CreateLambda(AddTeamCheats)); } void ULyraTeamSubsystem::Deinitialize() { UCheatManager::UnregisterFromOnCheatManagerCreated(CheatManagerRegistrationHandle); Super::Deinitialize(); } void ULyraTeamSubsystem::RegisterTeamInfo(ALyraTeamInfoBase* TeamInfo) { check(TeamInfo); const int32 TeamId = TeamInfo->GetTeamId(); check(TeamId != INDEX_NONE); FLyraTeamTrackingInfo& Entry = TeamMap.FindOrAdd(TeamId); Entry.SetTeamInfo(TeamInfo); } void ULyraTeamSubsystem::UnregisterTeamInfo(ALyraTeamInfoBase* TeamInfo) { check(TeamInfo); const int32 TeamId = TeamInfo->GetTeamId(); check(TeamId != INDEX_NONE); FLyraTeamTrackingInfo& Entry = TeamMap.FindChecked(TeamId); Entry.RemoveTeamInfo(TeamInfo); } bool ULyraTeamSubsystem::ChangeTeamForActor(AActor* ActorToChange, int32 NewTeamIndex) { const FGenericTeamId NewTeamID = IntegerToGenericTeamId(NewTeamIndex); if (ALyraPlayerState* LyraPS = const_cast(FindPlayerStateFromActor(ActorToChange))) { LyraPS->SetGenericTeamId(NewTeamID); return true; } else if (ILyraTeamAgentInterface* TeamActor = Cast(ActorToChange)) { TeamActor->SetGenericTeamId(NewTeamID); return true; } else { return false; } } int32 ULyraTeamSubsystem::FindTeamFromObject(const UObject* TestObject) const { // See if it's directly a team agent if (const ILyraTeamAgentInterface* ObjectWithTeamInterface = Cast(TestObject)) { return GenericTeamIdToInteger(ObjectWithTeamInterface->GetGenericTeamId()); } if (const AActor* TestActor = Cast(TestObject)) { // See if the instigator is a team actor if (const ILyraTeamAgentInterface* InstigatorWithTeamInterface = Cast(TestActor->GetInstigator())) { return GenericTeamIdToInteger(InstigatorWithTeamInterface->GetGenericTeamId()); } // Fall back to finding the associated player state if (const ALyraPlayerState* LyraPS = FindPlayerStateFromActor(TestActor)) { return LyraPS->GetTeamId(); } } return INDEX_NONE; } const ALyraPlayerState* ULyraTeamSubsystem::FindPlayerStateFromActor(const AActor* PossibleTeamActor) const { if (PossibleTeamActor != nullptr) { if (const APawn* Pawn = Cast(PossibleTeamActor)) { //@TODO: Consider an interface instead or have team actors register with the subsystem and have it maintain a map? (or LWC style) if (ALyraPlayerState* LyraPS = Pawn->GetPlayerState()) { return LyraPS; } } else if (const AController* PC = Cast(PossibleTeamActor)) { if (ALyraPlayerState* LyraPS = Cast(PC->PlayerState)) { return LyraPS; } } else if (const ALyraPlayerState* LyraPS = Cast(PossibleTeamActor)) { return LyraPS; } // Try the instigator // if (AActor* Instigator = PossibleTeamActor->GetInstigator()) // { // if (ensure(Instigator != PossibleTeamActor)) // { // return FindPlayerStateFromActor(Instigator); // } // } } return nullptr; } ELyraTeamComparison ULyraTeamSubsystem::CompareTeams(const UObject* A, const UObject* B, int32& TeamIdA, int32& TeamIdB) const { TeamIdA = FindTeamFromObject(Cast(A)); TeamIdB = FindTeamFromObject(Cast(B)); if ((TeamIdA == INDEX_NONE) || (TeamIdB == INDEX_NONE)) { return ELyraTeamComparison::InvalidArgument; } else { return (TeamIdA == TeamIdB) ? ELyraTeamComparison::OnSameTeam : ELyraTeamComparison::DifferentTeams; } } ELyraTeamComparison ULyraTeamSubsystem::CompareTeams(const UObject* A, const UObject* B) const { int32 TeamIdA; int32 TeamIdB; return CompareTeams(A, B, /*out*/ TeamIdA, /*out*/ TeamIdB); } void ULyraTeamSubsystem::FindTeamFromActor(const UObject* TestObject, bool& bIsPartOfTeam, int32& TeamId) const { TeamId = FindTeamFromObject(TestObject); bIsPartOfTeam = TeamId != INDEX_NONE; } void ULyraTeamSubsystem::AddTeamTagStack(int32 TeamId, FGameplayTag Tag, int32 StackCount) { auto FailureHandler = [&](const FString& ErrorMessage) { UE_LOG(LogLyraTeams, Error, TEXT("AddTeamTagStack(TeamId: %d, Tag: %s, StackCount: %d) %s"), TeamId, *Tag.ToString(), StackCount, *ErrorMessage); }; if (FLyraTeamTrackingInfo* Entry = TeamMap.Find(TeamId)) { if (Entry->PublicInfo) { if (Entry->PublicInfo->HasAuthority()) { Entry->PublicInfo->TeamTags.AddStack(Tag, StackCount); } else { FailureHandler(TEXT("failed because it was called on a client")); } } else { FailureHandler(TEXT("failed because there is no team info spawned yet (called too early, before the experience was ready)")); } } else { FailureHandler(TEXT("failed because it was passed an unknown team id")); } } void ULyraTeamSubsystem::RemoveTeamTagStack(int32 TeamId, FGameplayTag Tag, int32 StackCount) { auto FailureHandler = [&](const FString& ErrorMessage) { UE_LOG(LogLyraTeams, Error, TEXT("RemoveTeamTagStack(TeamId: %d, Tag: %s, StackCount: %d) %s"), TeamId, *Tag.ToString(), StackCount, *ErrorMessage); }; if (FLyraTeamTrackingInfo* Entry = TeamMap.Find(TeamId)) { if (Entry->PublicInfo) { if (Entry->PublicInfo->HasAuthority()) { Entry->PublicInfo->TeamTags.RemoveStack(Tag, StackCount); } else { FailureHandler(TEXT("failed because it was called on a client")); } } else { FailureHandler(TEXT("failed because there is no team info spawned yet (called too early, before the experience was ready)")); } } else { FailureHandler(TEXT("failed because it was passed an unknown team id")); } } int32 ULyraTeamSubsystem::GetTeamTagStackCount(int32 TeamId, FGameplayTag Tag) const { if (const FLyraTeamTrackingInfo* Entry = TeamMap.Find(TeamId)) { const int32 PublicStackCount = (Entry->PublicInfo != nullptr) ? Entry->PublicInfo->TeamTags.GetStackCount(Tag) : 0; const int32 PrivateStackCount = (Entry->PrivateInfo != nullptr) ? Entry->PrivateInfo->TeamTags.GetStackCount(Tag) : 0; return PublicStackCount + PrivateStackCount; } else { UE_LOG(LogLyraTeams, Verbose, TEXT("GetTeamTagStackCount(TeamId: %d, Tag: %s) failed because it was passed an unknown team id"), TeamId, *Tag.ToString()); return 0; } } bool ULyraTeamSubsystem::TeamHasTag(int32 TeamId, FGameplayTag Tag) const { return GetTeamTagStackCount(TeamId, Tag) > 0; } bool ULyraTeamSubsystem::DoesTeamExist(int32 TeamId) const { return TeamMap.Contains(TeamId); } TArray ULyraTeamSubsystem::GetTeamIDs() const { TArray Result; TeamMap.GenerateKeyArray(Result); Result.Sort(); return Result; } bool ULyraTeamSubsystem::CanCauseDamage(const UObject* Instigator, const UObject* Target, bool bAllowDamageToSelf) const { if (bAllowDamageToSelf) { if ((Instigator == Target) || (FindPlayerStateFromActor(Cast(Instigator)) == FindPlayerStateFromActor(Cast(Target)))) { return true; } } int32 InstigatorTeamId; int32 TargetTeamId; const ELyraTeamComparison Relationship = CompareTeams(Instigator, Target, /*out*/ InstigatorTeamId, /*out*/ TargetTeamId); if (Relationship == ELyraTeamComparison::DifferentTeams) { return true; } else if ((Relationship == ELyraTeamComparison::InvalidArgument) && (InstigatorTeamId != INDEX_NONE)) { // Allow damaging non-team actors for now, as long as they have an ability system component //@TODO: This is temporary until the target practice dummy has a team assignment return UAbilitySystemGlobals::GetAbilitySystemComponentFromActor(Cast(Target)) != nullptr; } return false; } ULyraTeamDisplayAsset* ULyraTeamSubsystem::GetTeamDisplayAsset(int32 TeamId, int32 ViewerTeamId) { // Currently ignoring ViewerTeamId if (FLyraTeamTrackingInfo* Entry = TeamMap.Find(TeamId)) { return Entry->DisplayAsset; } return nullptr; } ULyraTeamDisplayAsset* ULyraTeamSubsystem::GetEffectiveTeamDisplayAsset(int32 TeamId, UObject* ViewerTeamAgent) { return GetTeamDisplayAsset(TeamId, FindTeamFromObject(ViewerTeamAgent)); } void ULyraTeamSubsystem::NotifyTeamDisplayAssetModified(ULyraTeamDisplayAsset* /*ModifiedAsset*/) { // Broadcasting to all observers when a display asset is edited right now, instead of only the edited one for (const auto& KVP : TeamMap) { const int32 TeamId = KVP.Key; const FLyraTeamTrackingInfo& TrackingInfo = KVP.Value; TrackingInfo.OnTeamDisplayAssetChanged.Broadcast(TrackingInfo.DisplayAsset); } } FOnLyraTeamDisplayAssetChangedDelegate& ULyraTeamSubsystem::GetTeamDisplayAssetChangedDelegate(int32 TeamId) { return TeamMap.FindOrAdd(TeamId).OnTeamDisplayAssetChanged; }