diff --git a/RWTHVRCluster.uplugin b/RWTHVRCluster.uplugin
index 0297d365bc0735c759ccfc11f0aa5bb4763315e9..d5a51b05b2bf52b34d77c81db889a656220d5447 100644
--- a/RWTHVRCluster.uplugin
+++ b/RWTHVRCluster.uplugin
@@ -14,6 +14,10 @@
 	"IsBetaVersion": false,
 	"Installed": false,
 	"EnabledByDefault": true,
+	"SupportedTargetPlatforms": [
+		"Win64",
+		"Linux"
+	],
 	"Modules": [
 		{
 			"Name": "RWTHVRCluster",
@@ -26,10 +30,6 @@
 		}
 	],
 	"Plugins": [
-		{
-			"Name": "RWTHVRToolkit",
-			"Enabled": true
-		},
 		{
 			"Name": "nDisplay",
 			"Enabled": true,
diff --git a/Source/RWTHVRCluster/Private/CAVEOverlay/CAVEOverlayController.cpp b/Source/RWTHVRCluster/Private/CAVEOverlay/CAVEOverlayController.cpp
index 08d44b21a4e33283daa9a6a29b556beec0289cfa..4f27161686e7c6d20ca1b847da89a875e3c42e9c 100644
--- a/Source/RWTHVRCluster/Private/CAVEOverlay/CAVEOverlayController.cpp
+++ b/Source/RWTHVRCluster/Private/CAVEOverlay/CAVEOverlayController.cpp
@@ -4,16 +4,15 @@
 #include "EnhancedInputComponent.h"
 #include "IDisplayCluster.h"
 #include "MotionControllerComponent.h"
-#include "Camera/CameraComponent.h"
 #include "CAVEOverlay/DoorOverlayData.h"
+#include "Camera/CameraComponent.h"
 #include "Cluster/DisplayClusterClusterEvent.h"
 #include "Cluster/IDisplayClusterClusterManager.h"
 #include "Components/StaticMeshComponent.h"
 #include "Engine/CollisionProfile.h"
 #include "Logging/StructuredLog.h"
 #include "Materials/MaterialInstanceDynamic.h"
-#include "Pawn/RWTHVRPawn.h"
-#include "Utility/RWTHVRUtilities.h"
+#include "Utility/RWTHVRClusterUtilities.h"
 
 
 DEFINE_LOG_CATEGORY(LogCAVEOverlay);
@@ -50,8 +49,8 @@ ACAVEOverlayController::ACAVEOverlayController()
 	SetRootComponent(Root);
 
 	Tape = CreateMeshComponent("Tape", Root);
-	SignRightHand = CreateMeshComponent("SignRightHand", Root);
-	SignLeftHand = CreateMeshComponent("SignLeftHand", Root);
+	SignsStaticMeshComponents.Reserve(2);
+	MotionControllers.Reserve(2);
 }
 
 void ACAVEOverlayController::CycleDoorType()
@@ -158,11 +157,11 @@ void ACAVEOverlayController::BeginPlay()
 	// Not sure which place would be best...
 	const bool bValidPC = PC && PC->GetLocalPlayer();
 
-	if (!bValidPC || !URWTHVRUtilities::IsRoomMountedMode())
+	if (!bValidPC || !URWTHVRClusterUtilities::IsRoomMountedMode())
 		return;
 
 	// Input config
-	if (URWTHVRUtilities::IsPrimaryNode())
+	if (URWTHVRClusterUtilities::IsPrimaryNode())
 	{
 		if (CycleDoorTypeInputAction == nullptr)
 		{
@@ -219,9 +218,35 @@ void ACAVEOverlayController::BeginPlay()
 	Overlay->CornerText->SetText(FText::FromString(""));
 
 	// Get the pawn so we can have access to head and hand positions
-	VRPawn = Cast<ARWTHVRPawn>(PC->GetPawnOrSpectator());
-	if (VRPawn)
+	if (const auto* VRPawn = Cast<APawn>(PC->GetPawnOrSpectator()))
 	{
+		PawnCamera = VRPawn->GetComponentByClass<UCameraComponent>();
+		auto FoundMotionControllers = VRPawn->K2_GetComponentsByClass(UMotionControllerComponent::StaticClass());
+
+		for (const auto FoundMotionController : FoundMotionControllers)
+		{
+			if (auto* MC = Cast<UMotionControllerComponent>(FoundMotionController);
+				MC && MC->MotionSource != EName::None)
+			{
+				// Create new static mesh for them
+				auto* SignStaticMeshComp = NewObject<UStaticMeshComponent>();
+				SignStaticMeshComp->SetStaticMesh(SignStaticMesh);
+				SignStaticMeshComp->SetupAttachment(RootComponent);
+				SignStaticMeshComp->RegisterComponent();
+
+				MotionControllers.Add(MC);
+				SignsStaticMeshComponents.Add(SignStaticMeshComp);
+				SignsMIDs.Add(SignStaticMeshComp->CreateAndSetMaterialInstanceDynamic(0));
+			}
+		}
+
+		if (MotionControllers.Num() != 2)
+		{
+			UE_LOGFMT(LogCAVEOverlay, Display,
+					  "Found unexpected number of MotionControllers on Pawn. Expected 2, found {Number}. This might "
+					  "lead to weird results",
+					  MotionControllers.Num());
+		}
 		// we're good to go!
 		bInitialized = true;
 	}
@@ -230,10 +255,8 @@ void ACAVEOverlayController::BeginPlay()
 		UE_LOGFMT(LogCAVEOverlay, Error, "No VirtualRealityPawn found which we could attach to!");
 	}
 
-	// Create dynamic materials at runtime
+	// Create dynamic material for tape
 	TapeMaterialDynamic = Tape->CreateDynamicMaterialInstance(0);
-	RightSignMaterialDynamic = SignRightHand->CreateDynamicMaterialInstance(0);
-	LeftSignMaterialDynamic = SignLeftHand->CreateDynamicMaterialInstance(0);
 
 	UE_LOGFMT(LogCAVEOverlay, Display, "CaveOverlay Initialization was successfull.");
 }
@@ -317,41 +340,46 @@ void ACAVEOverlayController::Tick(float DeltaTime)
 	}
 
 	// Head/Tape Logic
-	const FVector HeadPosition = VRPawn->HeadCameraComponent->GetRelativeTransform().GetLocation();
-	const bool bHeadIsCloseToWall =
-		FMath::IsWithinInclusive(HeadPosition.GetAbsMax(), WallDistance - WallCloseDistance, WallDistance);
 
-	// Only show the tape when close to a wall and not within the door opening
-	if (bHeadIsCloseToWall && !PositionInDoorOpening(HeadPosition))
+	if (PawnCamera)
 	{
-		Tape->SetVisibility(true);
+		const FVector HeadPosition = PawnCamera->GetRelativeTransform().GetLocation();
+		const bool bHeadIsCloseToWall =
+			FMath::IsWithinInclusive(HeadPosition.GetAbsMax(), WallDistance - WallCloseDistance, WallDistance);
 
-		// Offset the tape in z direction to always be at head height
-		Tape->SetRelativeLocation(HeadPosition * FVector(0, 0, 1));
-
-		TapeMaterialDynamic->SetScalarParameterValue("BarrierOpacity", CalculateOpacityFromPosition(HeadPosition));
-
-		if (FMath::IsWithin(FVector2D(HeadPosition).GetAbsMax(), WallDistance - WallWarningDistance, WallDistance))
+		// Only show the tape when close to a wall and not within the door opening
+		if (bHeadIsCloseToWall && !PositionInDoorOpening(HeadPosition))
 		{
-			// in warning distance == red tape
-			TapeMaterialDynamic->SetVectorParameterValue("StripeColor", FVector(1, 0, 0));
+			Tape->SetVisibility(true);
+
+			// Offset the tape in z direction to always be at head height
+			Tape->SetRelativeLocation(HeadPosition * FVector(0, 0, 1));
+
+			TapeMaterialDynamic->SetScalarParameterValue("BarrierOpacity", CalculateOpacityFromPosition(HeadPosition));
+
+			if (FMath::IsWithin(FVector2D(HeadPosition).GetAbsMax(), WallDistance - WallWarningDistance, WallDistance))
+			{
+				// in warning distance == red tape
+				TapeMaterialDynamic->SetVectorParameterValue("StripeColor", FVector(1, 0, 0));
+			}
+			else
+			{
+				// otherwise we're just yellow
+				TapeMaterialDynamic->SetVectorParameterValue("StripeColor", FVector(1, 1, 0));
+			}
 		}
 		else
 		{
-			// otherwise we're just yellow
-			TapeMaterialDynamic->SetVectorParameterValue("StripeColor", FVector(1, 1, 0));
+			Tape->SetVisibility(false);
 		}
 	}
-	else
-	{
-		Tape->SetVisibility(false);
-	}
 
 	// Hand Logic
-	const FVector RightPosition = VRPawn->RightHand->GetRelativeTransform().GetLocation();
-	const FVector LeftPosition = VRPawn->LeftHand->GetRelativeTransform().GetLocation();
+	for (int i = 0; i < MotionControllers.Num(); i++)
+	{
+		const FVector HandPosition = MotionControllers[i]->GetRelativeLocation();
 
-	// Set the position rotation, opacity, visibility of the hand warning signs.
-	SetSignsForHand(SignRightHand, RightPosition, RightSignMaterialDynamic);
-	SetSignsForHand(SignLeftHand, LeftPosition, LeftSignMaterialDynamic);
+		// Set the position rotation, opacity, visibility of the hand warning signs.
+		SetSignsForHand(SignsStaticMeshComponents[i], HandPosition, SignsMIDs[i]);
+	}
 }
diff --git a/Source/RWTHVRCluster/Private/CaveSetup.cpp b/Source/RWTHVRCluster/Private/CaveSetup.cpp
index c39e47d4997863ee26b9fe5065c327efc6aa669f..63456d4309d4af0b21899b3e72431add8facae70 100644
--- a/Source/RWTHVRCluster/Private/CaveSetup.cpp
+++ b/Source/RWTHVRCluster/Private/CaveSetup.cpp
@@ -4,8 +4,7 @@
 #include "CaveSetup.h"
 
 #include "Logging/StructuredLog.h"
-#include "Utility/RWTHVRUtilities.h"
-
+#include "Utility/RWTHVRClusterUtilities.h"
 
 // Sets default values
 ACaveSetup::ACaveSetup()
@@ -22,7 +21,7 @@ void ACaveSetup::BeginPlay()
 {
 	Super::BeginPlay();
 
-	if (!URWTHVRUtilities::IsRoomMountedMode())
+	if (!URWTHVRClusterUtilities::IsRoomMountedMode())
 	{
 		return;
 	}
@@ -41,7 +40,7 @@ void ACaveSetup::BeginPlay()
 
 	// Apply the DTrack LiveLink Preset. Only do this if we are the primaryNode
 
-	if (URWTHVRUtilities::IsPrimaryNode())
+	if (URWTHVRClusterUtilities::IsPrimaryNode())
 	{
 		if (LiveLinkPresetToApplyOnCave && LiveLinkPresetToApplyOnCave->IsValidLowLevelFast())
 		{
diff --git a/Source/RWTHVRCluster/Private/Utility/RWTHVRClusterUtilities.cpp b/Source/RWTHVRCluster/Private/Utility/RWTHVRClusterUtilities.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f1f2a4cdb160a8ff0f543659602b54a1a1dba71a
--- /dev/null
+++ b/Source/RWTHVRCluster/Private/Utility/RWTHVRClusterUtilities.cpp
@@ -0,0 +1,112 @@
+#include "Utility/RWTHVRClusterUtilities.h"
+
+#include "DisplayClusterConfigurationTypes.h"
+#include "DisplayClusterRootActor.h"
+#include "IDisplayCluster.h"
+#include "Cluster/IDisplayClusterClusterManager.h"
+#include "Components/DisplayClusterCameraComponent.h"
+#include "Config/IDisplayClusterConfigManager.h"
+#include "Engine/Engine.h"
+#include "Engine/LocalPlayer.h"
+#include "Game/IDisplayClusterGameManager.h"
+
+DEFINE_LOG_CATEGORY(ClusterPlugin);
+
+bool URWTHVRClusterUtilities::IsRoomMountedMode()
+{
+	return IDisplayCluster::Get().GetOperationMode() == EDisplayClusterOperationMode::Cluster;
+}
+
+bool URWTHVRClusterUtilities::IsCave()
+{
+	if (!IsRoomMountedMode())
+		return false;
+
+	const UDisplayClusterConfigurationData* ClusterConfig = IDisplayCluster::Get().GetConfigMgr()->GetConfig();
+	return ClusterConfig->CustomParameters.Contains("Hardware_Platform") &&
+		ClusterConfig->CustomParameters.Find("Hardware_Platform")->Equals("aixcave", ESearchCase::IgnoreCase);
+}
+
+bool URWTHVRClusterUtilities::IsRolv()
+{
+	if (!IsRoomMountedMode())
+		return false;
+
+	const UDisplayClusterConfigurationData* ClusterConfig = IDisplayCluster::Get().GetConfigMgr()->GetConfig();
+	return ClusterConfig->CustomParameters.Contains("Hardware_Platform") &&
+		ClusterConfig->CustomParameters.Find("Hardware_Platform")->Equals("ROLV", ESearchCase::IgnoreCase);
+}
+
+/* Return true on the Primary in cluster mode and in a normal desktop session. Otherwise false */
+bool URWTHVRClusterUtilities::IsPrimaryNode()
+{
+	if (!IDisplayCluster::IsAvailable())
+	{
+		return true;
+	}
+	IDisplayClusterClusterManager* Manager = IDisplayCluster::Get().GetClusterMgr();
+	if (Manager == nullptr)
+	{
+		return true; // if we are not in cluster mode, we are always the primary node
+	}
+	return Manager->IsPrimary() || !Manager->IsSecondary();
+}
+
+bool URWTHVRClusterUtilities::IsSecondaryNode() { return !IsPrimaryNode(); }
+
+FString URWTHVRClusterUtilities::GetNodeName()
+{
+	return IsRoomMountedMode() ? IDisplayCluster::Get().GetClusterMgr()->GetNodeId() : FString(TEXT("Localhost"));
+}
+
+float URWTHVRClusterUtilities::GetEyeDistance()
+{
+	const ADisplayClusterRootActor* RootActor = IDisplayCluster::Get().GetGameMgr()->GetRootActor();
+	return (RootActor) ? RootActor->GetDefaultCamera()->GetInterpupillaryDistance() : 0.0f;
+}
+
+EDisplayClusterEyeStereoOffset URWTHVRClusterUtilities::GetNodeEyeType()
+{
+	const ADisplayClusterRootActor* RootActor = IDisplayCluster::Get().GetGameMgr()->GetRootActor();
+	return (RootActor) ? RootActor->GetDefaultCamera()->GetStereoOffset() : EDisplayClusterEyeStereoOffset::None;
+}
+
+USceneComponent* URWTHVRClusterUtilities::GetClusterComponent(const FString& Name)
+{
+	const ADisplayClusterRootActor* RootActor = IDisplayCluster::Get().GetGameMgr()->GetRootActor();
+	return (RootActor) ? RootActor->GetComponentByName<USceneComponent>(Name) : nullptr;
+}
+
+USceneComponent* URWTHVRClusterUtilities::GetNamedClusterComponent(const ENamedClusterComponent& Component)
+{
+	switch (Component)
+	{
+	case ENamedClusterComponent::NCC_CAVE_ORIGIN:
+		return GetClusterComponent("cave_origin");
+	case ENamedClusterComponent::NCC_CAVE_CENTER:
+		return GetClusterComponent("cave_center");
+	case ENamedClusterComponent::NCC_CAVE_LHT:
+		return GetClusterComponent("left_hand_target");
+	case ENamedClusterComponent::NCC_CAVE_RHT:
+		return GetClusterComponent("right_hand_target");
+	case ENamedClusterComponent::NCC_SHUTTERGLASSES:
+		return GetClusterComponent("shutter_glasses");
+	case ENamedClusterComponent::NCC_ROLV_ORIGIN:
+		return GetClusterComponent("rolv_origin");
+	case ENamedClusterComponent::NCC_FLYSTICK:
+		return GetClusterComponent("flystick");
+	case ENamedClusterComponent::NCC_CALIBRATIO:
+		return GetClusterComponent("calibratio");
+	case ENamedClusterComponent::NCC_TRACKING_ORIGIN:
+		USceneComponent* Result;
+		if ((Result = GetClusterComponent("cave_origin")))
+			return Result;
+		if ((Result = GetClusterComponent("rolv_origin")))
+			return Result;
+		if ((Result = GetClusterComponent("tdw_origin_floor")))
+			return Result;
+		return nullptr;
+	default:
+		return nullptr;
+	}
+}
diff --git a/Source/RWTHVRCluster/Public/CAVEOverlay/CAVEOverlayController.h b/Source/RWTHVRCluster/Public/CAVEOverlay/CAVEOverlayController.h
index 7740522090059295ef11a97762c35013edeaa3de..6d9828eab107bb37d48fb2d0d015c434f1077646 100644
--- a/Source/RWTHVRCluster/Public/CAVEOverlay/CAVEOverlayController.h
+++ b/Source/RWTHVRCluster/Public/CAVEOverlay/CAVEOverlayController.h
@@ -8,7 +8,10 @@
 #include "CAVEOverlayController.generated.h"
 
 
+class UMotionControllerComponent;
+class UCameraComponent;
 class ARWTHVRPawn;
+
 DECLARE_LOG_CATEGORY_EXTERN(LogCAVEOverlay, Log, All);
 
 /**
@@ -91,9 +94,12 @@ private:
 	// Only calculate positions and material values when we're fully initialized.
 	bool bInitialized = false;
 
-	// Reference to the currently active pawn that we're tracking positions of.
+	// Reference to the currently active pawn's camera and hands that we're tracking positions of.
+	UPROPERTY()
+	UCameraComponent* PawnCamera;
+
 	UPROPERTY()
-	ARWTHVRPawn* VRPawn;
+	TArray<UMotionControllerComponent*> MotionControllers;
 
 	// Cluster Events
 	FOnClusterEventJsonListener ClusterEventListenerDelegate;
@@ -114,13 +120,17 @@ public:
 	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true"))
 	UStaticMeshComponent* Tape;
 
-	// Right Hand Sign Static Mesh component. Reference to static mesh needs to be set in the corresponding BP.
+	// Right Hand Sign Static Mesh Reference
 	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true"))
-	UStaticMeshComponent* SignRightHand;
+	UStaticMesh* SignStaticMesh;
 
-	// Left Hand Sign Static Mesh component. Reference to static mesh needs to be set in the corresponding BP.
+	// Static Mesh Components for all tracked MotionControllers
 	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "CAVEOverlay", meta = (AllowPrivateAccess = "true"))
-	UStaticMeshComponent* SignLeftHand;
+	TArray<UStaticMeshComponent*> SignsStaticMeshComponents;
+
+	// Static Mesh Components for all tracked MotionControllers
+	UPROPERTY()
+	TArray<UMaterialInstanceDynamic*> SignsMIDs;
 
 	// UI Overlay
 	UPROPERTY(BlueprintReadOnly, EditAnywhere, Category = "CAVEOverlay")
@@ -136,10 +146,4 @@ public:
 	// Dynamic Materials to control opacity
 	UPROPERTY()
 	UMaterialInstanceDynamic* TapeMaterialDynamic;
-
-	UPROPERTY()
-	UMaterialInstanceDynamic* RightSignMaterialDynamic;
-
-	UPROPERTY()
-	UMaterialInstanceDynamic* LeftSignMaterialDynamic;
 };
diff --git a/Source/RWTHVRCluster/Public/Utility/RWTHVRClusterUtilities.h b/Source/RWTHVRCluster/Public/Utility/RWTHVRClusterUtilities.h
new file mode 100644
index 0000000000000000000000000000000000000000..cf2ad5a9a18b02a96d6747af552aa6b20b1b58c9
--- /dev/null
+++ b/Source/RWTHVRCluster/Public/Utility/RWTHVRClusterUtilities.h
@@ -0,0 +1,69 @@
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Components/DisplayClusterCameraComponent.h"
+#include "Kismet/BlueprintFunctionLibrary.h"
+#include "UObject/ConstructorHelpers.h"
+
+#include "RWTHVRClusterUtilities.generated.h"
+
+
+/**
+ * Custom log category for all ClusterPlugin related components
+ */
+DECLARE_LOG_CATEGORY_EXTERN(ClusterPlugin, Log, All);
+
+UENUM(BlueprintType)
+enum class ENamedClusterComponent : uint8
+{
+	/* CAVE Specific */
+	NCC_CAVE_ORIGIN UMETA(DisplayName = "CAVE Origin"),
+	NCC_CAVE_CENTER UMETA(DisplayName = "CAVE Center"),
+	NCC_CAVE_LHT UMETA(DisplayName = "CAVE Left Hand Target"),
+	NCC_CAVE_RHT UMETA(DisplayName = "CAVE Right Hand Target"),
+
+	/* ROLV Specific */
+	NCC_ROLV_ORIGIN UMETA(DisplayName = "ROLV Origin"),
+
+	/* Non Specific */
+	NCC_CALIBRATIO UMETA(DisplayName = "Calibratio Motion to Photon Measurement Device"),
+	NCC_SHUTTERGLASSES UMETA(DisplayName = "CAVE/ROLV/TDW Shutter Glasses"),
+	NCC_FLYSTICK UMETA(DisplayName = "CAVE/ROLV/TDW Flystick"),
+	NCC_TRACKING_ORIGIN UMETA(DisplayName = "CAVE/ROLV/TDW Origin")
+};
+
+UCLASS()
+class RWTHVRCLUSTER_API URWTHVRClusterUtilities : public UBlueprintFunctionLibrary
+{
+	GENERATED_BODY()
+
+public:
+	static bool IsRoomMountedMode();
+	UFUNCTION(BlueprintPure, Category = "DisplayCluster|Platform")
+	static bool IsCave();
+	UFUNCTION(BlueprintPure, Category = "DisplayCluster|Platform")
+	static bool IsRolv();
+
+	UFUNCTION(BlueprintPure, Category = "DisplayCluster")
+	static bool IsPrimaryNode();
+	UFUNCTION(BlueprintPure, Category = "DisplayCluster")
+	static bool IsSecondaryNode();
+
+	UFUNCTION(BlueprintPure, Category = "DisplayCluster")
+	static FString GetNodeName();
+	/* Distance in meters */
+	UFUNCTION(BlueprintPure, Category = "DisplayCluster")
+	static float GetEyeDistance();
+
+	UFUNCTION(BlueprintPure, Category = "DisplayCluster")
+	static EDisplayClusterEyeStereoOffset GetNodeEyeType();
+
+	//Get Component of Display Cluster by it's name, which is specified in the nDisplay config
+	UE_DEPRECATED(5.4, "GetClusterComponent has been removed because it is obsolete.")
+	UFUNCTION(BlueprintPure, BlueprintCallable, Category = "DisplayCluster", meta = (DeprecatedFunction))
+	static USceneComponent* GetClusterComponent(const FString& Name);
+
+	UE_DEPRECATED(5.4, "GetNamedClusterComponent has been removed because it is obsolete.")
+	UFUNCTION(BlueprintPure, BlueprintCallable, Category = "DisplayCluster", meta = (DeprecatedFunction))
+	static USceneComponent* GetNamedClusterComponent(const ENamedClusterComponent& Component);
+};
diff --git a/Source/RWTHVRCluster/RWTHVRCluster.Build.cs b/Source/RWTHVRCluster/RWTHVRCluster.Build.cs
index 12b3d9afcead6504a5624ff5ec0bdb031849f531..98f6200330450e0a35572838fe11bc02a88997e3 100644
--- a/Source/RWTHVRCluster/RWTHVRCluster.Build.cs
+++ b/Source/RWTHVRCluster/RWTHVRCluster.Build.cs
@@ -26,8 +26,8 @@ public class RWTHVRCluster : ModuleRules
 				"Slate",
 				"SlateCore",
 				"LiveLink", 
-				"DisplayCluster",
-				"RWTHVRToolkit"
+				"HeadMountedDisplay", // required for MotionControllerComp
+				"DisplayCluster"
 			}
 		);