diff --git a/Content/Pawn/BP_RWTHVRPawn_Default.uasset b/Content/Pawn/BP_RWTHVRPawn_Default.uasset index cb05e67f79561281c52e04b21e9bd67726871dbb..145ddf5f938d0a3612c7a609aafa75a30d82063f 100644 Binary files a/Content/Pawn/BP_RWTHVRPawn_Default.uasset and b/Content/Pawn/BP_RWTHVRPawn_Default.uasset differ diff --git a/Content/Pawn/Base/BP_RWTHVRPawn_Base.uasset b/Content/Pawn/Base/BP_RWTHVRPawn_Base.uasset index 3616819e69aa1ac0f21047b036be9244b1597c76..cba6c2e1875f45603f51e234dbb9b066017654f9 100644 Binary files a/Content/Pawn/Base/BP_RWTHVRPawn_Base.uasset and b/Content/Pawn/Base/BP_RWTHVRPawn_Base.uasset differ diff --git a/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp b/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp index 4d9b4b6e4dcdb1447e2d9ff98fbfc5400f000589..e2a1511392d96f9890bb52d309346d2465413593 100644 --- a/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp +++ b/Source/RWTHVRToolkit/Private/Core/RWTHVRGameModeBase.cpp @@ -7,9 +7,15 @@ #include "GameFramework/SpectatorPawn.h" #include "Kismet/GameplayStatics.h" #include "Logging/StructuredLog.h" +#include "Pawn/ClusterRepresentationActor.h" #include "Utility/RWTHVRUtilities.h" +ARWTHVRGameModeBase::ARWTHVRGameModeBase(const FObjectInitializer& ObjectInitializer) +{ + PlayerStateClass = ARWTHVRPlayerState::StaticClass(); +} + FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerController, const FUniqueNetIdRepl& UniqueId, const FString& Options, const FString& Portal) { @@ -17,6 +23,7 @@ FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerControlle // but I don't really want to introduce a hard dependency here. const FString NodeNameKey = "node"; const FString PrimaryNodeIdKey = "PrimaryNodeId"; + const FString ClusterIdKey = "ClusterId"; // Check if we're using our custom PlayerState so that we can save the player type there. // If not, just ingore all related args. @@ -24,6 +31,7 @@ FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerControlle if (State != nullptr) { + int32 ClusterId = -1; if (UGameplayStatics::HasOption(Options, PrimaryNodeIdKey)) { const FString PrimaryNodeId = UGameplayStatics::ParseOption(Options, PrimaryNodeIdKey); @@ -34,10 +42,22 @@ FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerControlle ? UGameplayStatics::ParseOption(Options, NodeNameKey) : PrimaryNodeId; + ClusterId = UGameplayStatics::HasOption(Options, ClusterIdKey) + ? FCString::Atoi(*UGameplayStatics::ParseOption(Options, ClusterIdKey)) + : -1; + const EPlayerType Type = NodeName == PrimaryNodeId ? EPlayerType::nDisplayPrimary : EPlayerType::nDisplaySecondary; State->RequestSetPlayerType(Type); } + else if (GetNetMode() == NM_Standalone && URWTHVRUtilities::IsRoomMountedMode()) + { + ClusterId = 0; + const EPlayerType Type = + URWTHVRUtilities::IsPrimaryNode() ? EPlayerType::nDisplayPrimary : EPlayerType::nDisplaySecondary; + State->RequestSetPlayerType(Type); + } + State->SetCorrespondingClusterId(ClusterId); } return Super::InitNewPlayer(NewPlayerController, UniqueId, Options, Portal); @@ -45,8 +65,47 @@ FString ARWTHVRGameModeBase::InitNewPlayer(APlayerController* NewPlayerControlle void ARWTHVRGameModeBase::PostLogin(APlayerController* NewPlayer) { - if (const ARWTHVRPlayerState* State = Cast<ARWTHVRPlayerState>(NewPlayer->PlayerState); State != nullptr) + if (ARWTHVRPlayerState* State = Cast<ARWTHVRPlayerState>(NewPlayer->PlayerState); State != nullptr) { + // If we're in none-standalone netmode, this is only executed on the server, as the GM only exists there. + // On standalone, this is executed on every node. + int32 ClusterId = State->GetCorrespondingClusterId(); + if (ClusterId >= 0) // we're either standalone (0) or in an acutal cluster + { + AClusterRepresentationActor** ClusterRepresentationPtr = ConnectedClusters.Find(ClusterId); + AClusterRepresentationActor* ClusterRepresentation; + if (!ClusterRepresentationPtr) + { + // No actor there yet, spawn it + FActorSpawnParameters SpawnParameters; + SpawnParameters.Name = FName(*FString::Printf(TEXT("ClusterRepresentation_%d"), ClusterId)); + SpawnParameters.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested; + ClusterRepresentation = GetWorld()->SpawnActor<AClusterRepresentationActor>(SpawnParameters); + UE_LOGFMT(Toolkit, Display, + "ARWTHVRGameModeBase: Spawned ClusterRepresentationActor {Name} for Cluster {Id}", + ClusterRepresentation->GetName(), ClusterId); + ConnectedClusters.Add(ClusterId, ClusterRepresentation); + } + else + { + ClusterRepresentation = *ClusterRepresentationPtr; + } + + UE_LOGFMT(Toolkit, Display, "ARWTHVRGameModeBase: Using ClusterRepresentationActor {Name} for Cluster {Id}", + *ClusterRepresentation->GetName(), ClusterId); + + // Double check for sanity + check(ClusterRepresentation != nullptr); + + State->SetCorrespondingClusterActor(ClusterRepresentation); + + if (State->GetPlayerType() == EPlayerType::nDisplayPrimary) + { + // We're the owner of the actor! + ClusterRepresentation->SetOwner(NewPlayer); + } + } + // Do we already have an auto-possessing pawn possessed? if (NewPlayer->GetPawn() && NewPlayer->GetPawn()->IsValidLowLevelFast()) { @@ -56,10 +115,11 @@ void ARWTHVRGameModeBase::PostLogin(APlayerController* NewPlayer) return; } + // When we're not in standalone: // If the new player is a secondary nDisplay node, spawn it only as a Spectator // Potentially we can use MustSpectate instead. UClass* PawnClass; - if (State->GetPlayerType() == EPlayerType::nDisplaySecondary) + if (GetNetMode() != NM_Standalone && State->GetPlayerType() == EPlayerType::nDisplaySecondary) { // For now, simply use the BP approach of spawning the pawn here. Can do this in a better way potentially. PawnClass = SpectatorClass; @@ -88,6 +148,5 @@ void ARWTHVRGameModeBase::PostLogin(APlayerController* NewPlayer) NewPlayer->Possess(ResultPawn); } - Super::PostLogin(NewPlayer); } diff --git a/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp b/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp index 0ad60f0c44a7853df65507cd23b9943c6cd8530b..4ae4795c85058331aeea4a2ff08df643b3cccd05 100644 --- a/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp +++ b/Source/RWTHVRToolkit/Private/Core/RWTHVRPlayerState.cpp @@ -3,8 +3,10 @@ #include "Core/RWTHVRPlayerState.h" +#include "Logging/StructuredLog.h" #include "Net/UnrealNetwork.h" #include "Net/Core/PushModel/PushModel.h" +#include "Utility/RWTHVRUtilities.h" // Boilerplate, copies properties to new state void ARWTHVRPlayerState::CopyProperties(class APlayerState* PlayerState) @@ -17,6 +19,8 @@ void ARWTHVRPlayerState::CopyProperties(class APlayerState* PlayerState) if (IsValid(RWTHVRPlayerState)) { RWTHVRPlayerState->SetPlayerType(GetPlayerType()); + RWTHVRPlayerState->SetCorrespondingClusterId(CorrespondingClusterId); + RWTHVRPlayerState->SetCorrespondingClusterActor(CorrespondingClusterActor); } } } @@ -32,6 +36,8 @@ void ARWTHVRPlayerState::OverrideWith(class APlayerState* PlayerState) if (IsValid(RWTHVRPlayerState)) { SetPlayerType(RWTHVRPlayerState->GetPlayerType()); + SetCorrespondingClusterId(RWTHVRPlayerState->CorrespondingClusterId); + SetCorrespondingClusterActor(RWTHVRPlayerState->CorrespondingClusterActor); } } } @@ -45,6 +51,8 @@ void ARWTHVRPlayerState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& O SharedParams.bIsPushBased = true; DOREPLIFETIME_WITH_PARAMS_FAST(ARWTHVRPlayerState, PlayerType, SharedParams); + DOREPLIFETIME_WITH_PARAMS_FAST(ARWTHVRPlayerState, CorrespondingClusterId, SharedParams); + DOREPLIFETIME_WITH_PARAMS_FAST(ARWTHVRPlayerState, CorrespondingClusterActor, SharedParams); } void ARWTHVRPlayerState::ServerSetPlayerTypeRpc_Implementation(const EPlayerType NewPlayerType) @@ -57,6 +65,28 @@ void ARWTHVRPlayerState::SetPlayerType(const EPlayerType NewPlayerType) MARK_PROPERTY_DIRTY_FROM_NAME(ARWTHVRPlayerState, PlayerType, this); PlayerType = NewPlayerType; } +void ARWTHVRPlayerState::SetCorrespondingClusterId(int32 NewCorrespondingClusterId) +{ + if (!HasAuthority()) + { + UE_LOGFMT(Toolkit, Warning, "ARWTHVRPlayerState: Cannot set cluster Id on non-authority!"); + return; + } + + MARK_PROPERTY_DIRTY_FROM_NAME(ARWTHVRPlayerState, CorrespondingClusterId, this); + CorrespondingClusterId = NewCorrespondingClusterId; +} +void ARWTHVRPlayerState::SetCorrespondingClusterActor(AClusterRepresentationActor* NewCorrespondingClusterActor) +{ + if (!HasAuthority()) + { + UE_LOGFMT(Toolkit, Warning, "ARWTHVRPlayerState: Cannot set cluster actor ref on non-authority!"); + return; + } + + MARK_PROPERTY_DIRTY_FROM_NAME(ARWTHVRPlayerState, CorrespondingClusterActor, this); + CorrespondingClusterActor = NewCorrespondingClusterActor; +} void ARWTHVRPlayerState::RequestSetPlayerType(const EPlayerType NewPlayerType) { diff --git a/Source/RWTHVRToolkit/Private/Pawn/ClusterRepresentationActor.cpp b/Source/RWTHVRToolkit/Private/Pawn/ClusterRepresentationActor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a280d911113902d4918555b0a6b425d31e97ff8e --- /dev/null +++ b/Source/RWTHVRToolkit/Private/Pawn/ClusterRepresentationActor.cpp @@ -0,0 +1,170 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Pawn/ClusterRepresentationActor.h" +#include "Core/RWTHVRPlayerState.h" +#include "Kismet/GameplayStatics.h" +#include "Logging/StructuredLog.h" +#include "Utility/RWTHVRUtilities.h" + +#if PLATFORM_SUPPORTS_CLUSTER +#include "DisplayClusterRootActor.h" +#include "IDisplayCluster.h" +#include "Config/IDisplayClusterConfigManager.h" +#include "Game/IDisplayClusterGameManager.h" +#endif + +// Sets default values +AClusterRepresentationActor::AClusterRepresentationActor() +{ + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = false; + bReplicates = true; + SetActorEnableCollision(false); + + SetRootComponent(CreateDefaultSubobject<USceneComponent>(TEXT("Origin"))); +} + +void AClusterRepresentationActor::BeginPlay() +{ + Super::BeginPlay(); + // will fail if we're in replicated mode and PlayerState has not yet replicated fully + // Therefore we also execute this + AttachDCRAIfRequired(); +} + +void AClusterRepresentationActor::AttachDCRAIfRequired(const ARWTHVRPlayerState* OptionalPlayerState) +{ + // We need to identify the correct ClusterRepresentationActor to do the attachment. + // 1. Either we are the local net owner -> Primary Node Pawn + // This is hard to do as things might not be synchronized yet. + // In this case I think this is save to do because either: + // - We are in standalone mode, then attach it for all nodes + // - If we are a client, this actor has been spawned on the server only. Therefore I assume that if we + // have replicated this actor to our client, we're good to go. + +#if PLATFORM_SUPPORTS_CLUSTER + + if (!URWTHVRUtilities::IsRoomMountedMode()) + return; + + if (bIsAttached) + { + UE_LOGFMT(Toolkit, Display, "{Name} AttachDCRAIfRequired: Already attached, skipping repeated attachment.", + GetName()); + return; + } + + UE_LOGFMT(Toolkit, Display, "{Name} AttachDCRAIfRequired: Starting DCRA Attachment process.", GetName()); + + // This should give us the first local player controller + const auto* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0); + + // Only run this for the local controller - redundant, but double check + if (!PlayerController || !PlayerController->IsLocalController()) + { + UE_LOGFMT(Toolkit, Warning, + "{Name} AttachDCRAIfRequired: PlayerController not valid or not locally controlled.", GetName()); + return; + } + const auto* PlayerState = + OptionalPlayerState != nullptr ? OptionalPlayerState : PlayerController->GetPlayerState<ARWTHVRPlayerState>(); + if (!PlayerState) + { + UE_LOGFMT(Toolkit, Error, + "{Name} AttachDCRAIfRequired: PlayerState is not valid or not of type " + "ARWTHVRPlayerState.", + GetName()); + return; + } + + const auto CCA = PlayerState->GetCorrespondingClusterActor(); + + if (CCA == nullptr) // this can happen often if property isn't replicated yet, this is okay. + return; + + UE_LOGFMT(Toolkit, Display, + "{Name} AttachDCRAIfRequired: Player State is {PlayerState}, PlayerState->CCA is {CCA}.", GetName(), + PlayerState->GetName(), CCA->GetName()); + + // The local player this is executed on corresponds to this actor + if (CCA == this) + { + UE_LOGFMT(Toolkit, Display, "{Name} AttachDCRAIfRequired: Attaching DCRA to {Name}.", GetName(), + CCA->GetName()); + + bIsAttached = AttachDCRA(); + } +#endif +} + +#if PLATFORM_SUPPORTS_CLUSTER +bool AClusterRepresentationActor::AttachDCRA() +{ + if (URWTHVRUtilities::IsRoomMountedMode()) + { + UE_LOGFMT(Toolkit, Display, "{Name}: Trying to attach DCRA", GetName()); + auto DCRA = IDisplayCluster::Get().GetGameMgr()->GetRootActor(); + + if (!IsValid(DCRA)) + { + UE_LOGFMT(Toolkit, Display, "No Valid DCRA in BeginPlay. Spawning manually."); + + DCRA = SpawnDCRA(); + if (!IsValid(DCRA)) + { + UE_LOGFMT(Toolkit, Error, "Failed to spawn correct DCRA, cannot attach it to ClusterRepresentation."); + return false; + } + } + else // if we just spawned the DCRA, it is not yet the primary one and this check makes no sense + { + if (!DCRA->IsPrimaryRootActor()) + { + UE_LOGFMT(Toolkit, Error, "Found DCRA {Name} is not the primary DCRA of Cluster with Id!", + DCRA->GetName()); + return false; + } + } + + bool bAttached = DCRA->AttachToActor(this, FAttachmentTransformRules::SnapToTargetNotIncludingScale); + UE_LOGFMT(Toolkit, Display, "Attaching DCRA {DCRA} to {this} returned {Res}", DCRA->GetName(), GetName(), + bAttached); + + DCRA->SetActorEnableCollision(false); + } + return true; +} + +ADisplayClusterRootActor* AClusterRepresentationActor::SpawnDCRA() +{ + UDisplayClusterConfigurationData* ConfigData = IDisplayCluster::Get().GetConfigMgr()->GetConfig(); + + + // Function similar to the one from DisplayClusterGameManager. + ADisplayClusterRootActor* RootActor = nullptr; + // We need to use generated class as it's the only one available in packaged buidls + const FString AssetPath = + (ConfigData->Info.AssetPath.EndsWith(TEXT("_C")) ? ConfigData->Info.AssetPath + : ConfigData->Info.AssetPath + TEXT("_C")); + + if (UClass* ActorClass = Cast<UClass>(StaticLoadObject(UClass::StaticClass(), nullptr, *AssetPath))) + { + // Spawn the actor + if (AActor* NewActor = GetWorld()->SpawnActor<AActor>(ActorClass)) + { + RootActor = Cast<ADisplayClusterRootActor>(NewActor); + + if (RootActor) + { + UE_LOG(Toolkit, Log, TEXT("Instantiated DCRA from asset '%s'"), *ConfigData->Info.AssetPath); + + // Override actor settings in case the config file contains some updates + RootActor->OverrideFromConfig(ConfigData); + } + } + } + return RootActor; +} + +#endif diff --git a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp index d50578531749b295d79961ac45eab7ebfd097c7e..810a6205268b8ebdec7ba7922d1ba697cbe96f88 100644 --- a/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp +++ b/Source/RWTHVRToolkit/Private/Pawn/RWTHVRPawn.cpp @@ -8,8 +8,8 @@ #include "ILiveLinkClient.h" #include "InputMappingContext.h" #include "Core/RWTHVRPlayerState.h" -#include "Kismet/GameplayStatics.h" #include "Logging/StructuredLog.h" +#include "Pawn/ClusterRepresentationActor.h" #include "Pawn/InputExtensionInterface.h" #include "Pawn/Navigation/CollisionHandlingMovement.h" #include "Pawn/ReplicatedCameraComponent.h" @@ -61,7 +61,7 @@ void ARWTHVRPawn::Tick(float DeltaSeconds) * as connections now send their playertype over. */ // This pawn's controller has changed! This is called on both server and owning client. If we are the owning client -// and the master, request that the DCRA is attached to us. +// and the master, request that the Cluster is attached to us. void ARWTHVRPawn::NotifyControllerChanged() { Super::NotifyControllerChanged(); @@ -71,17 +71,17 @@ void ARWTHVRPawn::NotifyControllerChanged() if (HasAuthority()) { UE_LOG(Toolkit, Display, - TEXT("ARWTHVRPawn: Player Controller has changed, trying to change DCRA attachment if possible...")); + TEXT("ARWTHVRPawn: Player Controller has changed, trying to change Cluster attachment if possible...")); if (const ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>()) { const EPlayerType Type = State->GetPlayerType(); // Only cluster types are valid here as they are set on connection. // For all other player types this is a race condition - if (Type == EPlayerType::nDisplayPrimary || GetNetMode() == NM_Standalone) + if (Type == EPlayerType::nDisplayPrimary || Type == EPlayerType::nDisplaySecondary) { - UE_LOGFMT(Toolkit, Display, "ARWTHVRPawn: Attaching DCRA to Pawn {Pawn}.", GetName()); - AttachDCRAtoPawn(); + UE_LOGFMT(Toolkit, Display, "ARWTHVRPawn: Attaching Cluster to Pawn {Pawn}.", GetName()); + AttachClustertoPawn(); } } } @@ -259,35 +259,35 @@ void ARWTHVRPawn::MulticastAddDCSyncComponent_Implementation() #endif } -// Executed on the server only: Finds and attaches the CaveSetup Actor, which contains the DCRA to the Pawn. +// Executed on the server only: Attaches the ClusterRepresentation Actor, which contains the DCRA to the Pawn. // It is only executed on the server because attachments are synced to all clients, but not from client to server. -void ARWTHVRPawn::AttachDCRAtoPawn() +void ARWTHVRPawn::AttachClustertoPawn() { - if (!CaveSetupActorClass || !CaveSetupActorClass->IsValidLowLevelFast()) + if (const ARWTHVRPlayerState* State = GetPlayerState<ARWTHVRPlayerState>()) { - UE_LOGFMT(Toolkit, Warning, "No CaveSetup Actor class set in pawn!"); - return; - } - - // Find our CaveSetup actor - TArray<AActor*> FoundActors; - UGameplayStatics::GetAllActorsOfClass(GetWorld(), CaveSetupActorClass, FoundActors); - - if (!FoundActors.IsEmpty()) - { - const auto CaveSetupActor = FoundActors[0]; - FAttachmentTransformRules AttachmentRules = FAttachmentTransformRules::SnapToTargetNotIncludingScale; - CaveSetupActor->AttachToActor(this, AttachmentRules); - UE_LOGFMT(Toolkit, Display, "VirtualRealityPawn: Attaching CaveSetup to our pawn!"); + const auto ClusterActor = State->GetCorrespondingClusterActor(); + if (!ClusterActor) + { + UE_LOGFMT( + Toolkit, Error, + "ARWTHVRPawn::AttachClustertoPawn: GetCorrespondingClusterActor returned null! This won't work on " + "the Cave."); + return; + } + const FAttachmentTransformRules AttachmentRules = FAttachmentTransformRules::SnapToTargetNotIncludingScale; + bool bAttached = ClusterActor->AttachToComponent(GetRootComponent(), AttachmentRules); + // State->GetCorrespondingClusterActor()->OnAttached(); + UE_LOGFMT(Toolkit, Display, + "ARWTHVRPawn: Attaching corresponding cluster actor to our pawn returned: {Attached}", bAttached); } else { - UE_LOGFMT(Toolkit, Warning, - "No CaveSetup Actor found which can be attached to the Pawn! This won't work on the Cave."); + UE_LOGFMT(Toolkit, Error, + "ARWTHVRPawn::AttachClustertoPawn: No ARWTHVRPlayerState set! This won't work on the Cave."); } - // if (HasAuthority()) // Should always be the case here, but double check - // MulticastAddDCSyncComponent(); + if (HasAuthority()) // Should always be the case here, but double check + MulticastAddDCSyncComponent(); } void ARWTHVRPawn::SetupMotionControllerSources() diff --git a/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h b/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h index 1469ac36caa9b571b765622766a01ed103b26252..be8e65d9da82cf7acc215bc8154ec9772fc7537c 100644 --- a/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h +++ b/Source/RWTHVRToolkit/Public/Core/RWTHVRGameModeBase.h @@ -4,6 +4,7 @@ #include "CoreMinimal.h" #include "GameFramework/GameModeBase.h" +#include "Pawn/ClusterRepresentationActor.h" #include "RWTHVRGameModeBase.generated.h" /** @@ -15,6 +16,8 @@ UCLASS() class RWTHVRTOOLKIT_API ARWTHVRGameModeBase : public AGameModeBase { GENERATED_BODY() +public: + ARWTHVRGameModeBase(const FObjectInitializer& ObjectInitializer); protected: /** @@ -29,4 +32,8 @@ protected: * possess. If not, spawn a DefaultPawnClass Pawn and Possess it (Should be BP_VirtualRealityPawn to make sense). */ virtual void PostLogin(APlayerController* NewPlayer) override; + +private: + UPROPERTY() + TMap<int32, AClusterRepresentationActor*> ConnectedClusters; }; diff --git a/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h b/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h index 4c709e204428e4cbcd20d782823676c5b8e12c7c..040c251a6db94e6564b83d120bfd62d7bb6d57e0 100644 --- a/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h +++ b/Source/RWTHVRToolkit/Public/Core/RWTHVRPlayerState.h @@ -5,8 +5,13 @@ #include "CoreMinimal.h" #include "PlayerType.h" #include "GameFramework/PlayerState.h" +#include "Logging/StructuredLog.h" +#include "Pawn/ClusterRepresentationActor.h" +#include "Utility/RWTHVRUtilities.h" #include "RWTHVRPlayerState.generated.h" + +class AClusterRepresentationActor; enum class EPlayerType : uint8; /** * Extension of the PlayerState that additionally holds information about what type the player is. @@ -22,6 +27,26 @@ private: UPROPERTY(Replicated, Category = PlayerState, BlueprintGetter = GetPlayerType, meta = (AllowPrivateAccess)) EPlayerType PlayerType = EPlayerType::Desktop; + /** Replicated cluster ID for this player. Is -1 in case player is not a cluster*/ + UPROPERTY(Replicated, Category = PlayerState, BlueprintGetter = GetCorrespondingClusterId, + meta = (AllowPrivateAccess)) + int32 CorrespondingClusterId = -1; + + /** Replicated cluster actor for this player. Is nullptr in case player is not a cluster. + * As this is not guaranteed to be valid on BeginPlay, we need to do a callback to the CorrespondingClusterActor + * here... + */ + UPROPERTY(ReplicatedUsing = OnRep_CorrespondingClusterActor) + TObjectPtr<AClusterRepresentationActor> CorrespondingClusterActor; + + UFUNCTION() + virtual void OnRep_CorrespondingClusterActor() + { + // Only execute this on the local owning player + if (HasLocalNetOwner()) + CorrespondingClusterActor->AttachDCRAIfRequired(this); + } + UFUNCTION(Reliable, Server) void ServerSetPlayerTypeRpc(EPlayerType NewPlayerType); @@ -31,6 +56,15 @@ public: UFUNCTION(BlueprintGetter) EPlayerType GetPlayerType() const { return PlayerType; } + UFUNCTION(BlueprintGetter) + int32 GetCorrespondingClusterId() const { return CorrespondingClusterId; } + + UFUNCTION(BlueprintGetter) + AClusterRepresentationActor* GetCorrespondingClusterActor() const { return CorrespondingClusterActor; } + + void SetCorrespondingClusterId(int32 NewCorrespondingClusterId); + void SetCorrespondingClusterActor(AClusterRepresentationActor* NewCorrespondingClusterActor); + UFUNCTION(BlueprintCallable) void RequestSetPlayerType(EPlayerType NewPlayerType); diff --git a/Source/RWTHVRToolkit/Public/Pawn/ClusterRepresentationActor.h b/Source/RWTHVRToolkit/Public/Pawn/ClusterRepresentationActor.h new file mode 100644 index 0000000000000000000000000000000000000000..7826cb001ff5b5ea035f3e6b96920c3edb215138 --- /dev/null +++ b/Source/RWTHVRToolkit/Public/Pawn/ClusterRepresentationActor.h @@ -0,0 +1,36 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + +#include "ClusterRepresentationActor.generated.h" + + +class ARWTHVRPlayerState; +#if PLATFORM_SUPPORTS_CLUSTER +class ADisplayClusterRootActor; +#endif + +UCLASS() +class RWTHVRTOOLKIT_API AClusterRepresentationActor : public AActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + AClusterRepresentationActor(); + + virtual void BeginPlay() override; + + void AttachDCRAIfRequired(const ARWTHVRPlayerState* OptionalPlayerState = nullptr); + +private: + bool bIsAttached = false; + +#if PLATFORM_SUPPORTS_CLUSTER + bool AttachDCRA(); + ADisplayClusterRootActor* SpawnDCRA(); + +#endif +}; diff --git a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h index 260cdc0408e5f2d1ef9d64c7f56ba4325a8ff123..cd81e497181dcfef757b067ccae8c862c022fa34 100644 --- a/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h +++ b/Source/RWTHVRToolkit/Public/Pawn/RWTHVRPawn.h @@ -79,10 +79,6 @@ public: UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Pawn|LiveLink") bool bWorldTransform = false; - /* The class which to search for DCRA attachment. TODO: Make this better it's ugly */ - UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "Pawn|LiveLink") - TSubclassOf<AActor> CaveSetupActorClass; - protected: virtual void SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) override; void AddInputMappingContext(const APlayerController* PC, const UInputMappingContext* Context) const; @@ -107,8 +103,8 @@ protected: UFUNCTION(Reliable, NetMulticast) void MulticastAddDCSyncComponent(); - /* Attaches the DCRA to the pawn */ - void AttachDCRAtoPawn(); + /* Attaches the Cluster representation to the pawn */ + void AttachClustertoPawn(); /* Set device specific motion controller sources (None, L/R, Livelink) */ void SetupMotionControllerSources();