371 lines
11 KiB
C++
371 lines
11 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "LyraTeamSubsystem.h"
|
|
#include "Net/UnrealNetwork.h"
|
|
#include "LyraTeamPublicInfo.h"
|
|
#include "LyraTeamPrivateInfo.h"
|
|
#include "GameFramework/Controller.h"
|
|
#include "Player/LyraPlayerState.h"
|
|
#include "GameFramework/Pawn.h"
|
|
#include "AbilitySystemGlobals.h"
|
|
#include "LyraTeamCheats.h"
|
|
#include "LyraTeamAgentInterface.h"
|
|
#include "LyraLogChannels.h"
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
// FLyraTeamTrackingInfo
|
|
|
|
void FLyraTeamTrackingInfo::SetTeamInfo(ALyraTeamInfoBase* Info)
|
|
{
|
|
if (ALyraTeamPublicInfo* NewPublicInfo = Cast<ALyraTeamPublicInfo>(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<ALyraTeamPrivateInfo>(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<ULyraTeamCheats>(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<ALyraPlayerState*>(FindPlayerStateFromActor(ActorToChange)))
|
|
{
|
|
LyraPS->SetGenericTeamId(NewTeamID);
|
|
return true;
|
|
}
|
|
else if (ILyraTeamAgentInterface* TeamActor = Cast<ILyraTeamAgentInterface>(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<ILyraTeamAgentInterface>(TestObject))
|
|
{
|
|
return GenericTeamIdToInteger(ObjectWithTeamInterface->GetGenericTeamId());
|
|
}
|
|
|
|
if (const AActor* TestActor = Cast<const AActor>(TestObject))
|
|
{
|
|
// See if the instigator is a team actor
|
|
if (const ILyraTeamAgentInterface* InstigatorWithTeamInterface = Cast<ILyraTeamAgentInterface>(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<const APawn>(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<ALyraPlayerState>())
|
|
{
|
|
return LyraPS;
|
|
}
|
|
}
|
|
else if (const AController* PC = Cast<const AController>(PossibleTeamActor))
|
|
{
|
|
if (ALyraPlayerState* LyraPS = Cast<ALyraPlayerState>(PC->PlayerState))
|
|
{
|
|
return LyraPS;
|
|
}
|
|
}
|
|
else if (const ALyraPlayerState* LyraPS = Cast<const ALyraPlayerState>(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<const AActor>(A));
|
|
TeamIdB = FindTeamFromObject(Cast<const AActor>(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<int32> ULyraTeamSubsystem::GetTeamIDs() const
|
|
{
|
|
TArray<int32> 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<AActor>(Instigator)) == FindPlayerStateFromActor(Cast<AActor>(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<const AActor>(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;
|
|
}
|