diff --git a/Config/DefaultRWTHVRToolkit.ini b/Config/DefaultRWTHVRToolkit.ini
index 13066ebe26e8ab454486f97bbb982275d9b2587c..50643d31a5dd9100fd5e107efa705fae6d70c639 100644
--- a/Config/DefaultRWTHVRToolkit.ini
+++ b/Config/DefaultRWTHVRToolkit.ini
@@ -18,4 +18,10 @@
 +FunctionRedirects = (OldName="/Script/RWTHVRToolkit.DirectInteractionComponent.OnBeginGrab",NewName="/Script/RWTHVRToolkit.DirectInteractionComponent.OnBeginInteraction")
 +FunctionRedirects = (OldName="/Script/RWTHVRToolkit.DirectInteractionComponent.OnEndGrab",NewName="/Script/RWTHVRToolkit.DirectInteractionComponent.OnEndInteraction")
 +PropertyRedirects = (OldName="/Script/RWTHVRToolkit.DirectInteractionComponent.PreviousGrabBehavioursInRange",NewName="/Script/RWTHVRToolkit.DirectInteractionComponent.PreviousInteractableComponentsInRange")
-+PropertyRedirects = (OldName="/Script/RWTHVRToolkit.DirectInteractionComponent.CurrentGrabBehavioursInRange",NewName="/Script/RWTHVRToolkit.DirectInteractionComponent.CurrentInteractableComponentsInRange")
\ No newline at end of file
++PropertyRedirects = (OldName="/Script/RWTHVRToolkit.DirectInteractionComponent.CurrentGrabBehavioursInRange",NewName="/Script/RWTHVRToolkit.DirectInteractionComponent.CurrentInteractableComponentsInRange")
++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.UBaseInteractionComponent.OnBeginInteraction",NewName="/Script/RWTHVRToolkit.UBaseInteractionComponent.OnBeginInteractionInputAction")
++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.UBaseInteractionComponent.OnEndInteraction",NewName="/Script/RWTHVRToolkit.UBaseInteractionComponent.OnEndInteractionInputAction")
++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.UBaseInteractionComponent.MulticastHoverBehaviourStartRpc",NewName="/Script/RWTHVRToolkit.UBaseInteractionComponent.MulticastHoverBehaviourReplicationStartRpc")
++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.UBaseInteractionComponent.MulticastActionBehaviourStartRpc",NewName="/Script/RWTHVRToolkit.UBaseInteractionComponent.MulticastActionBehaviourReplicationStartRpc")
++FunctionRedirects=(OldName="/Script/RWTHVRToolkit.ActionBehaviour.OnActionStart",NewName="/Script/RWTHVRToolkit.ActionBehaviour.OnActionEvent")
++PropertyRedirects=(OldName="/Script/RWTHVRToolkit.ActionBehaviour.OnActionBeginEvent",NewName="/Script/RWTHVRToolkit.ActionBehaviour.OnActionEventEvent")
\ No newline at end of file
diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/ActionBehaviour.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/ActionBehaviour.cpp
index 4e9c3fd662dd3baa2a30fddd8f516022e4efa9ba..ff1d027208025476ce073a15bd224ca5c9ccb9f0 100644
--- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/ActionBehaviour.cpp
+++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/ActionBehaviour.cpp
@@ -6,27 +6,14 @@
 // We disable ticking here, as we are mainly interested in the events
 UActionBehaviour::UActionBehaviour() { PrimaryComponentTick.bCanEverTick = false; }
 
-void UActionBehaviour::OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction,
+void UActionBehaviour::OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType,
 									 const FInputActionValue& Value)
 {
 }
 
-void UActionBehaviour::OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction,
-								   const FInputActionValue& Value)
-{
-}
-
 void UActionBehaviour::BeginPlay()
 {
 	Super::BeginPlay();
 
-	OnActionBeginEvent.AddDynamic(this, &UActionBehaviour::OnActionStart);
-	OnActionEndEvent.AddDynamic(this, &UActionBehaviour::OnActionEnd);
-}
-
-// Called every frame
-void UActionBehaviour::TickComponent(float DeltaTime, ELevelTick TickType,
-									 FActorComponentTickFunction* ThisTickFunction)
-{
-	Super::TickComponent(DeltaTime, TickType, ThisTickFunction);
+	OnActionEventEvent.AddDynamic(this, &UActionBehaviour::OnActionEvent);
 }
diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/BaseBehaviour.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/BaseBehaviour.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..95488260668c095eb5dece6895555ce808e2fd11
--- /dev/null
+++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/BaseBehaviour.cpp
@@ -0,0 +1,4 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "Interaction/Interactables/BaseBehaviour.h"
diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp
index 1537243da0b6c92a901decadce793bc8bd819bbb..7afd6522eda1b041118e012dd46b9946851a1133 100644
--- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp
+++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/GrabBehavior.cpp
@@ -3,6 +3,7 @@
 
 #include "Interaction/Interactables/GrabBehavior.h"
 #include "Interaction/Interactables/InteractableComponent.h"
+#include "Interaction/Interactables/InteractionEventType.h"
 #include "Logging/StructuredLog.h"
 #include "Serialization/JsonTypes.h"
 #include "Utility/RWTHVRUtilities.h"
@@ -34,15 +35,46 @@ UPrimitiveComponent* UGrabBehavior::GetHighestParentSimulatingPhysics(UPrimitive
 	return Comp;
 }
 
-void UGrabBehavior::OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction,
+void UGrabBehavior::OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType,
 								  const FInputActionValue& Value)
+{
+	if (EventType == EInteractionEventType::InteractionStart)
+	{
+		StartGrab(TriggerComponent);
+	}
+	else
+	{
+		EndGrab(TriggerComponent);
+	}
+}
+
+bool UGrabBehavior::TryRelease()
+{
+	if (!bObjectGrabbed)
+	{
+		return false;
+	}
+
+	if (MyPhysicsComponent)
+	{
+		MyPhysicsComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
+		MyPhysicsComponent->SetSimulatePhysics(bWasSimulatingPhysics);
+	}
+	else
+	{
+		GetOwner()->GetRootComponent()->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
+	}
+	bObjectGrabbed = false;
+	return true;
+}
+void UGrabBehavior::StartGrab(USceneComponent* TriggerComponent)
 {
 	if (bObjectGrabbed)
 	{
 		return;
 	}
 
-	USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggeredComponent->GetAttachParent());
+	USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggerComponent->GetAttachParent());
 	const FAttachmentTransformRules Rules = FAttachmentTransformRules(EAttachmentRule::KeepWorld, false);
 
 	if (MyPhysicsComponent = GetFirstComponentSimulatingPhysics(GetOwner()); MyPhysicsComponent != nullptr)
@@ -72,18 +104,16 @@ void UGrabBehavior::OnActionStart(USceneComponent* TriggeredComponent, const UIn
 		GetOwner()->GetComponents<UInteractableComponent>(Interactables, false);
 		for (UInteractableComponent* Interactable : Interactables)
 		{
-			Interactable->RestrictInteractionToComponent(TriggeredComponent);
+			Interactable->RestrictInteractionToComponent(TriggerComponent);
 		}
 	}
 
 	OnGrabStartEvent.Broadcast(CurrentAttachParent, MyPhysicsComponent);
 }
 
-void UGrabBehavior::OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction,
-								const FInputActionValue& Value)
+void UGrabBehavior::EndGrab(USceneComponent* TriggerComponent)
 {
-
-	USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggeredComponent->GetAttachParent());
+	USceneComponent* CurrentAttachParent = Cast<USceneComponent>(TriggerComponent->GetAttachParent());
 
 	// We try to release the attached component. If it is not succesful we log and return. Otherwise, we continue.
 	if (!TryRelease())
@@ -107,23 +137,3 @@ void UGrabBehavior::OnActionEnd(USceneComponent* TriggeredComponent, const UInpu
 		}
 	}
 }
-
-bool UGrabBehavior::TryRelease()
-{
-	if (!bObjectGrabbed)
-	{
-		return false;
-	}
-
-	if (MyPhysicsComponent)
-	{
-		MyPhysicsComponent->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
-		MyPhysicsComponent->SetSimulatePhysics(bWasSimulatingPhysics);
-	}
-	else
-	{
-		GetOwner()->GetRootComponent()->DetachFromComponent(FDetachmentTransformRules::KeepWorldTransform);
-	}
-	bObjectGrabbed = false;
-	return true;
-}
diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/HoverBehaviour.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/HoverBehaviour.cpp
index 96897613cd2f536cb9625a9b395ce49181656e70..f78e75fabfa434b1396d7bffc1354adee7f49441 100644
--- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/HoverBehaviour.cpp
+++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/HoverBehaviour.cpp
@@ -3,14 +3,13 @@
 
 #include "Interaction/Interactables/HoverBehaviour.h"
 
-void UHoverBehaviour::OnHoverStart(const USceneComponent* TriggeredComponent, FHitResult Hit) {}
-
-void UHoverBehaviour::OnHoverEnd(const USceneComponent* TriggeredComponent) {}
 
+void UHoverBehaviour::OnHoverEvent(const USceneComponent* TriggerComponent, EInteractionEventType EventType,
+								   const FHitResult& Hit)
+{
+}
 void UHoverBehaviour::BeginPlay()
 {
 	Super::BeginPlay();
-
-	OnHoverStartEvent.AddDynamic(this, &UHoverBehaviour::OnHoverStart);
-	OnHoverEndEvent.AddDynamic(this, &UHoverBehaviour::OnHoverEnd);
+	OnHoverEventEvent.AddDynamic(this, &UHoverBehaviour::OnHoverEvent);
 }
diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/IntenSelect/IntenSelectable.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/IntenSelect/IntenSelectable.cpp
index b813e2f9c2906079c2718140c84a78b41d4ebb92..276269b3dc0f389621e6415a71887cf3170a9e79 100644
--- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/IntenSelect/IntenSelectable.cpp
+++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/IntenSelect/IntenSelectable.cpp
@@ -62,7 +62,8 @@ void UIntenSelectable::HandleOnSelectStartEvents(const UIntenSelectComponent* In
 {
 	for (const UHoverBehaviour* CurrentHoverBehaviour : OnHoverBehaviours)
 	{
-		CurrentHoverBehaviour->OnHoverStartEvent.Broadcast(IntenSelect, HitResult);
+		CurrentHoverBehaviour->OnHoverEventEvent.Broadcast(IntenSelect, EInteractionEventType::InteractionStart,
+														   HitResult);
 	}
 }
 
@@ -70,7 +71,9 @@ void UIntenSelectable::HandleOnSelectEndEvents(const UIntenSelectComponent* Inte
 {
 	for (const UHoverBehaviour* CurrentHoverBehaviour : OnHoverBehaviours)
 	{
-		CurrentHoverBehaviour->OnHoverEndEvent.Broadcast(IntenSelect);
+		FHitResult EmptyHit;
+		CurrentHoverBehaviour->OnHoverEventEvent.Broadcast(IntenSelect, EInteractionEventType::InteractionEnd,
+														   EmptyHit);
 	}
 }
 
@@ -79,8 +82,8 @@ void UIntenSelectable::HandleOnClickStartEvents(UIntenSelectComponent* IntenSele
 	for (const UActionBehaviour* CurrentActionBehaviour : OnActionBehaviours)
 	{
 		FInputActionValue EmptyInputActionValue{};
-		const UInputAction* EmptyInputAction{};
-		CurrentActionBehaviour->OnActionBeginEvent.Broadcast(IntenSelect, EmptyInputAction, EmptyInputActionValue);
+		CurrentActionBehaviour->OnActionEventEvent.Broadcast(IntenSelect, EInteractionEventType::InteractionStart,
+															 EmptyInputActionValue);
 	}
 }
 
@@ -88,8 +91,8 @@ void UIntenSelectable::HandleOnClickEndEvents(UIntenSelectComponent* IntenSelect
 {
 	for (const UActionBehaviour* CurrentActionBehaviour : OnActionBehaviours)
 	{
-		const UInputAction* EmptyInputActionValue{};
-		CurrentActionBehaviour->OnActionEndEvent.Broadcast(IntenSelect, EmptyInputActionValue, InputValue);
+		CurrentActionBehaviour->OnActionEventEvent.Broadcast(IntenSelect, EInteractionEventType::InteractionEnd,
+															 InputValue);
 	}
 }
 
@@ -98,7 +101,7 @@ void UIntenSelectable::InitDefaultBehaviourReferences()
 	// Scoring
 
 	TSet<UActorComponent*> AllComponents = GetOwner()->GetComponents();
-	for (UActorComponent * c : AllComponents)
+	for (UActorComponent* c : AllComponents)
 	{
 		if (UIntenSelectableScoring* TryToGetScoring = Cast<UIntenSelectableScoring>(c))
 		{
diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp
index 29ee75b08e1d37147671228e167c09e4cde462f8..1e954ee1cef0511a89a3dd6d4f846a0a176ee767 100644
--- a/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp
+++ b/Source/RWTHVRToolkit/Private/Interaction/Interactables/InteractableComponent.cpp
@@ -4,6 +4,11 @@
 #include "Interaction/Interactables/InteractableComponent.h"
 #include "Interaction/Interactables/ActionBehaviour.h"
 #include "Interaction/Interactables/HoverBehaviour.h"
+#include "Interaction/Interactors/UBaseInteractionComponent.h"
+#include "Logging/StructuredLog.h"
+#include "Utility/RWTHVRUtilities.h"
+
+bool UInteractableComponent::HasInteractionTypeFlag(EInteractorType type) { return type & InteractorFilter; }
 
 void UInteractableComponent::RestrictInteractionToComponents(const TArray<USceneComponent*>& Components)
 {
@@ -39,9 +44,8 @@ void UInteractableComponent::BeginPlay()
 	InitDefaultBehaviourReferences();
 }
 
-// This functions dispatches the HoverStart Event to the attached Hover Behaviour Components
-void UInteractableComponent::HandleOnHoverStartEvents(USceneComponent* TriggerComponent,
-													  const EInteractorType Interactor)
+void UInteractableComponent::HandleOnHoverEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor,
+												 const EInteractionEventType EventType)
 {
 	// We early return if there the InteractorFilter is set and the Interactor is allowed.
 	if (!(InteractorFilter == EInteractorType::None || InteractorFilter & Interactor))
@@ -52,34 +56,44 @@ void UInteractableComponent::HandleOnHoverStartEvents(USceneComponent* TriggerCo
 		return;
 
 	// Broadcast event to all HoverBehaviours
-	for (const UHoverBehaviour* b : OnHoverBehaviours)
+	for (UHoverBehaviour* HoverBehaviour : OnHoverBehaviours)
 	{
-		b->OnHoverStartEvent.Broadcast(TriggerComponent, HitResult);
-	}
-}
-
-// This functions dispatches the HoverEnd Event to the attached Hover Behaviour Components
-void UInteractableComponent::HandleOnHoverEndEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor)
-{
-	// We early return if there the InteractorFilter is set and the Interactor is allowed.
-	if (!(InteractorFilter == EInteractorType::None || InteractorFilter & Interactor))
-		return;
-
-	// We early return if the source Interactor is not part of the allowed components
-	if (!IsComponentAllowed(TriggerComponent))
-		return;
-
-	// Broadcast event to all HoverBehaviours
-	for (const UHoverBehaviour* b : OnHoverBehaviours)
-	{
-		b->OnHoverEndEvent.Broadcast(TriggerComponent);
+		// Check if we need to replicate the action to the server
+		if (HoverBehaviour->bExecuteOnServer)
+		{
+			// Request the server to execute our behaviour.
+			// As we can only execute RPCs from an owned object, and we don't necessarily own this actor, pipe it
+			// through the interactor. Because behaviours can also be triggered from non-interactors, only do this on a
+			// successful cast
+
+			auto* InteractorComponent = Cast<UUBaseInteractionComponent>(TriggerComponent);
+			if (!InteractorComponent)
+			{
+				UE_LOGFMT(Toolkit, Warning,
+						  "Interaction: TriggerComponent {TriggerComponent} is not a UUBaseInteractionComponent. Only "
+						  "UUBaseInteractionComponent TriggerComponents can be replicated.",
+						  TriggerComponent->GetName());
+				return;
+			}
+			InteractorComponent->RequestHoverBehaviourReplicationStart(HoverBehaviour, EventType, HitResult);
+		}
+		else if (HoverBehaviour->bExecuteOnAllClients)
+		{
+			UE_LOGFMT(Toolkit, Warning,
+					  "Interaction: Behaviour {BehaviourName} has bExecuteOnAllClients=true, which requires "
+					  "bExecuteOnServer also set to true, which is not the case.",
+					  HoverBehaviour->GetName());
+		}
+		else // skip replication altogether (default case)
+		{
+			HoverBehaviour->OnHoverEventEvent.Broadcast(TriggerComponent, EventType, HitResult);
+		}
 	}
 }
 
 // This functions dispatches the ActionStart Event to the attached Action Behaviour Components
-void UInteractableComponent::HandleOnActionStartEvents(USceneComponent* TriggerComponent,
-													   const UInputAction* InputAction, const FInputActionValue& Value,
-													   const EInteractorType Interactor)
+void UInteractableComponent::HandleOnActionEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor,
+												  const EInteractionEventType EventType, const FInputActionValue& Value)
 {
 	// We early return if there the InteractorFilter is set and the Interactor is allowed.
 	if (!(InteractorFilter == EInteractorType::None || InteractorFilter & Interactor))
@@ -90,28 +104,38 @@ void UInteractableComponent::HandleOnActionStartEvents(USceneComponent* TriggerC
 		return;
 
 	// Broadcast event to all ActionBehaviours
-	for (const UActionBehaviour* b : OnActionBehaviours)
+	for (UActionBehaviour* ActionBehaviour : OnActionBehaviours)
 	{
-		b->OnActionBeginEvent.Broadcast(TriggerComponent, InputAction, Value);
-	}
-}
-
-// This functions dispatches the ActionEnd Event to the attached Action Behaviour Components
-void UInteractableComponent::HandleOnActionEndEvents(USceneComponent* TriggerComponent, const UInputAction* InputAction,
-													 const FInputActionValue& Value, const EInteractorType Interactor)
-{
-	// We early return if there the InteractorFilter is set and the Interactor is allowed.
-	if (!(InteractorFilter == EInteractorType::None || InteractorFilter & Interactor))
-		return;
-
-	// We early return if the source Interactor is not part of the allowed components
-	if (!IsComponentAllowed(TriggerComponent))
-		return;
-
-	// Broadcast event to all ActionBehaviours
-	for (const UActionBehaviour* b : OnActionBehaviours)
-	{
-		b->OnActionEndEvent.Broadcast(TriggerComponent, InputAction, Value);
+		// Check if we need to replicate the action to the server
+		if (ActionBehaviour->bExecuteOnServer)
+		{
+			// Request the server to execute our behaviour.
+			// As we can only execute RPCs from an owned object, and we don't necessarily own this actor, pipe it
+			// through the interactor. Because behaviours can also be triggered from non-interactors, only do this on a
+			// successful cast
+
+			auto* InteractorComponent = Cast<UUBaseInteractionComponent>(TriggerComponent);
+			if (!InteractorComponent)
+			{
+				UE_LOGFMT(Toolkit, Warning,
+						  "Interaction: TriggerComponent {TriggerComponent} is not a UUBaseInteractionComponent. Only "
+						  "UUBaseInteractionComponent TriggerComponents can be replicated.",
+						  TriggerComponent->GetName());
+				return;
+			}
+			InteractorComponent->RequestActionBehaviourReplicationStart(ActionBehaviour, EventType, Value);
+		}
+		else if (ActionBehaviour->bExecuteOnAllClients)
+		{
+			UE_LOGFMT(Toolkit, Warning,
+					  "Interaction: Behaviour {BehaviourName} has bExecuteOnAllClients=true, which requires "
+					  "bExecuteOnServer also set to true, which is not the case.",
+					  ActionBehaviour->GetName());
+		}
+		else // skip replication altogether (default case)
+		{
+			ActionBehaviour->OnActionEventEvent.Broadcast(TriggerComponent, EventType, Value);
+		}
 	}
 }
 
@@ -150,4 +174,4 @@ bool UInteractableComponent::IsComponentAllowed(USceneComponent* Component) cons
 		}
 	}
 	return true;
-}
+}
\ No newline at end of file
diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp
index c8cf9ce35e45d0bc107cd73da08333ef5ba5b2d9..4b7af7690e9093ac4f20dda70a3c3951f2f317cf 100644
--- a/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp
+++ b/Source/RWTHVRToolkit/Private/Interaction/Interactors/DirectInteractionComponent.cpp
@@ -6,6 +6,7 @@
 #include "EnhancedInputComponent.h"
 #include "Interaction/Interactables/InteractableComponent.h"
 #include "Interaction/Interactables/InteractionBitSet.h"
+#include "Interaction/Interactables/InteractionEventType.h"
 
 #include "Kismet/GameplayStatics.h"
 #include "Logging/StructuredLog.h"
@@ -30,7 +31,7 @@ void UDirectInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tick
 	TArray<FHitResult> OutHits;
 	const ETraceTypeQuery TraceType = UEngineTypes::ConvertToTraceType(ECollisionChannel::ECC_PhysicsBody);
 
-	auto DebugTrace = bShowDebugTrace ? EDrawDebugTrace::ForOneFrame : EDrawDebugTrace::None;
+	const auto DebugTrace = bShowDebugTrace ? EDrawDebugTrace::ForOneFrame : EDrawDebugTrace::None;
 
 	UKismetSystemLibrary::SphereTraceMulti(GetWorld(), GetAttachParent()->GetComponentLocation(),
 										   GetAttachParent()->GetComponentLocation(), InteractionSphereRadius,
@@ -59,7 +60,8 @@ void UDirectInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tick
 		if (!PreviousInteractableComponentsInRange.Contains(CurrentInteractableComp))
 		{
 			PreviousInteractableComponentsInRange.AddUnique(CurrentInteractableComp);
-			CurrentInteractableComp->HandleOnHoverStartEvents(this, EInteractorType::Direct);
+			CurrentInteractableComp->HandleOnHoverEvents(this, EInteractorType::Direct,
+														 EInteractionEventType::InteractionStart);
 		}
 	}
 
@@ -71,7 +73,8 @@ void UDirectInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tick
 		if (!CurrentInteractableCompsInRange.Contains(PrevInteractableComp))
 		{
 			ComponentsToRemove.AddUnique(PrevInteractableComp);
-			PrevInteractableComp->HandleOnHoverEndEvents(this, EInteractorType::Direct);
+			PrevInteractableComp->HandleOnHoverEvents(this, EInteractorType::Direct,
+													  EInteractionEventType::InteractionEnd);
 		}
 	}
 
@@ -97,12 +100,12 @@ void UDirectInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInputC
 		return;
 
 	EI->BindAction(InteractionInputAction, ETriggerEvent::Started, this,
-				   &UDirectInteractionComponent::OnBeginInteraction);
+				   &UDirectInteractionComponent::OnBeginInteractionInputAction);
 	EI->BindAction(InteractionInputAction, ETriggerEvent::Completed, this,
-				   &UDirectInteractionComponent::OnEndInteraction);
+				   &UDirectInteractionComponent::OnEndInteractionInputAction);
 }
 
-void UDirectInteractionComponent::OnBeginInteraction(const FInputActionValue& Value)
+void UDirectInteractionComponent::OnBeginInteractionInputAction(const FInputActionValue& Value)
 {
 	const FVector InteractionLocation = GetAttachParent()->GetComponentLocation();
 
@@ -115,7 +118,7 @@ void UDirectInteractionComponent::OnBeginInteraction(const FInputActionValue& Va
 			CurrentInteractableComponentsInRange,
 			[&](auto Element)
 			{ return FVector(Element->GetOwner()->GetActorLocation() - InteractionLocation).Size(); });
-		MinElement->HandleOnActionStartEvents(this, InteractionInputAction, Value, EInteractorType::Direct);
+		MinElement->HandleOnActionEvents(this, EInteractorType::Direct, EInteractionEventType::InteractionStart, Value);
 		CurrentlyInteractedComponents = {MinElement};
 	}
 	else
@@ -124,19 +127,21 @@ void UDirectInteractionComponent::OnBeginInteraction(const FInputActionValue& Va
 											  CurrentInteractableComponentsInRange.Num());
 		for (UInteractableComponent* InteractableComp : CurrentInteractableComponentsInRange)
 		{
-			InteractableComp->HandleOnActionStartEvents(this, InteractionInputAction, Value, EInteractorType::Direct);
+			InteractableComp->HandleOnActionEvents(this, EInteractorType::Direct,
+												   EInteractionEventType::InteractionStart, Value);
 			CurrentlyInteractedComponents.Add(InteractableComp);
 		}
 	}
 }
 
-void UDirectInteractionComponent::OnEndInteraction(const FInputActionValue& Value)
+void UDirectInteractionComponent::OnEndInteractionInputAction(const FInputActionValue& Value)
 {
 	for (auto& Component : CurrentlyInteractedComponents)
 	{
 		if (Component.IsValid())
 		{
-			Component->HandleOnActionEndEvents(this, InteractionInputAction, Value, EInteractorType::Direct);
+			Component->HandleOnActionEvents(this, EInteractorType::Direct, EInteractionEventType::InteractionStart,
+											Value);
 		}
 	}
 }
diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactors/RaycastInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactors/RaycastInteractionComponent.cpp
index 94afba4b4426fe1b861d787826f0e1cc11d6583b..42e1aeea61a94302b199769e8f79d50314c5cc89 100644
--- a/Source/RWTHVRToolkit/Private/Interaction/Interactors/RaycastInteractionComponent.cpp
+++ b/Source/RWTHVRToolkit/Private/Interaction/Interactors/RaycastInteractionComponent.cpp
@@ -5,6 +5,7 @@
 
 #include "EnhancedInputComponent.h"
 #include "Interaction/Interactables/InteractableComponent.h"
+#include "Interaction/Interactables/InteractionEventType.h"
 #include "Kismet/KismetSystemLibrary.h"
 
 // Sets default values for this component's properties
@@ -25,14 +26,13 @@ void URaycastInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tic
 
 	UInteractableComponent* NewInteractableComponent = nullptr;
 
-
-	TArray<AActor*> ActorsToIgnore;
 	FHitResult Hit;
 	const ETraceTypeQuery TraceType = UEngineTypes::ConvertToTraceType(ECollisionChannel::ECC_PhysicsBody);
-	FVector TraceStart = GetAttachParent()->GetComponentLocation();
-	FVector TraceEnd = GetAttachParent()->GetComponentLocation() + TraceLength * GetAttachParent()->GetForwardVector();
+	const FVector TraceStart = GetAttachParent()->GetComponentLocation();
+	const FVector TraceEnd =
+		GetAttachParent()->GetComponentLocation() + TraceLength * GetAttachParent()->GetForwardVector();
 
-	auto DebugTrace = bShowDebugTrace ? EDrawDebugTrace::ForOneFrame : EDrawDebugTrace::None;
+	const auto DebugTrace = bShowDebugTrace ? EDrawDebugTrace::ForOneFrame : EDrawDebugTrace::None;
 
 	UKismetSystemLibrary::LineTraceSingle(GetWorld(), TraceStart, TraceEnd, TraceType, true, ActorsToIgnore, DebugTrace,
 										  Hit, true, FColor::Green);
@@ -53,24 +53,28 @@ void URaycastInteractionComponent::TickComponent(float DeltaTime, ELevelTick Tic
 	if (CurrentInteractable != PreviousInteractable)
 	{
 		if (CurrentInteractable && CurrentInteractable->HasInteractionTypeFlag(EInteractorType::Raycast))
-			CurrentInteractable->HandleOnHoverStartEvents(this, EInteractorType::Raycast);
+			CurrentInteractable->HandleOnHoverEvents(this, EInteractorType::Raycast,
+													 EInteractionEventType::InteractionStart);
 		if (PreviousInteractable && PreviousInteractable->HasInteractionTypeFlag(EInteractorType::Raycast))
-			PreviousInteractable->HandleOnHoverEndEvents(this, EInteractorType::Raycast);
+			PreviousInteractable->HandleOnHoverEvents(this, EInteractorType::Raycast,
+													  EInteractionEventType::InteractionEnd);
 	}
 
 	PreviousInteractable = CurrentInteractable;
 }
 
-void URaycastInteractionComponent::OnBeginInteraction(const FInputActionValue& Value)
+void URaycastInteractionComponent::OnBeginInteractionInputAction(const FInputActionValue& Value)
 {
 	if (CurrentInteractable && CurrentInteractable->HasInteractionTypeFlag(EInteractorType::Raycast))
-		CurrentInteractable->HandleOnActionStartEvents(this, InteractionInputAction, Value, EInteractorType::Raycast);
+		CurrentInteractable->HandleOnActionEvents(this, EInteractorType::Raycast,
+												  EInteractionEventType::InteractionStart, Value);
 }
 
-void URaycastInteractionComponent::OnEndInteraction(const FInputActionValue& Value)
+void URaycastInteractionComponent::OnEndInteractionInputAction(const FInputActionValue& Value)
 {
 	if (CurrentInteractable && CurrentInteractable->HasInteractionTypeFlag(EInteractorType::Raycast))
-		CurrentInteractable->HandleOnActionEndEvents(this, InteractionInputAction, Value, EInteractorType::Raycast);
+		CurrentInteractable->HandleOnActionEvents(this, EInteractorType::Raycast, EInteractionEventType::InteractionEnd,
+												  Value);
 }
 
 void URaycastInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent)
@@ -86,7 +90,7 @@ void URaycastInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInput
 		return;
 
 	EI->BindAction(InteractionInputAction, ETriggerEvent::Started, this,
-				   &URaycastInteractionComponent::OnBeginInteraction);
+				   &URaycastInteractionComponent::OnBeginInteractionInputAction);
 	EI->BindAction(InteractionInputAction, ETriggerEvent::Completed, this,
-				   &URaycastInteractionComponent::OnEndInteraction);
+				   &URaycastInteractionComponent::OnEndInteractionInputAction);
 }
diff --git a/Source/RWTHVRToolkit/Private/Interaction/Interactors/UBaseInteractionComponent.cpp b/Source/RWTHVRToolkit/Private/Interaction/Interactors/UBaseInteractionComponent.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..67d718c90cdff0dce7dab7cf7bff226f778877c9
--- /dev/null
+++ b/Source/RWTHVRToolkit/Private/Interaction/Interactors/UBaseInteractionComponent.cpp
@@ -0,0 +1,152 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+
+#include "Interaction/Interactors/UBaseInteractionComponent.h"
+
+#include "Interaction/Interactables/ActionBehaviour.h"
+#include "Interaction/Interactables/HoverBehaviour.h"
+#include "Logging/StructuredLog.h"
+#include "Utility/RWTHVRUtilities.h"
+
+// Sets default values for this component's properties
+UUBaseInteractionComponent::UUBaseInteractionComponent()
+{
+	// Set this component to be initialized when the game starts, and to be ticked every frame.  You can turn these
+	// features off to improve performance if you don't need them.
+	PrimaryComponentTick.bCanEverTick = true;
+	SetIsReplicatedByDefault(true);
+	// ...
+}
+
+void UUBaseInteractionComponent::SetupPlayerInput(UInputComponent* PlayerInputComponent)
+{
+	IInputExtensionInterface::SetupPlayerInput(PlayerInputComponent);
+}
+
+void UUBaseInteractionComponent::OnBeginInteractionInputAction(const FInputActionValue& Value) {}
+
+void UUBaseInteractionComponent::OnEndInteractionInputAction(const FInputActionValue& Value) {}
+
+void UUBaseInteractionComponent::RequestHoverBehaviourReplicationStart(UHoverBehaviour* Behaviour,
+																	   const EInteractionEventType EventType,
+																	   const FHitResult& Hit)
+{
+	if (GetOwner()->HasLocalNetOwner())
+	{
+		if (GetOwner()->HasAuthority())
+		{
+			HoverBehaviourReplicationStart(Behaviour, EventType, Hit);
+		}
+		else
+		{
+			// RPC
+			ServerRequestHoverBehaviourReplicationStartRpc(Behaviour, EventType, Hit);
+		}
+	}
+	else
+	{
+		UE_LOGFMT(Toolkit, Error,
+				  "Interaction: Trying to replicate HoverBehaviour {HoverBehaviour} in InteractionComponent "
+				  "{InteractionComponent} which has no local net owner!",
+				  Behaviour->GetName(), this->GetName());
+	}
+}
+
+void UUBaseInteractionComponent::HoverBehaviourReplicationStart(UHoverBehaviour* Behaviour,
+																const EInteractionEventType EventType,
+																const FHitResult& Hit)
+{
+	if (!Behaviour->bExecuteOnServer) // this should never happen
+	{
+		UE_LOGFMT(Toolkit, Error,
+				  "Interaction: Trying to execute HoverBehaviour {HoverBehaviour} in InteractionComponent "
+				  "{InteractionComponent} on the server, but bExecuteOnServer is false!",
+				  Behaviour->GetName(), this->GetName());
+		return;
+	}
+
+	// If desired, multicast to all clients (including originator)
+	if (Behaviour->bExecuteOnAllClients)
+	{
+		MulticastHoverBehaviourReplicationStartRpc(Behaviour, EventType, Hit);
+	}
+	else
+	{
+		// Execute the behaviour only on the server
+		Behaviour->OnHoverEventEvent.Broadcast(this, EventType, Hit);
+	}
+}
+
+void UUBaseInteractionComponent::ServerRequestHoverBehaviourReplicationStartRpc_Implementation(
+	UHoverBehaviour* Behaviour, const EInteractionEventType EventType, const FHitResult& Hit)
+{
+	HoverBehaviourReplicationStart(Behaviour, EventType, Hit);
+}
+
+void UUBaseInteractionComponent::MulticastHoverBehaviourReplicationStartRpc_Implementation(
+	UHoverBehaviour* Behaviour, const EInteractionEventType EventType, const FHitResult& Hit)
+{
+	Behaviour->OnHoverEventEvent.Broadcast(this, EventType, Hit);
+}
+
+void UUBaseInteractionComponent::RequestActionBehaviourReplicationStart(UActionBehaviour* Behaviour,
+																		const EInteractionEventType EventType,
+																		const FInputActionValue& Value)
+{
+	if (GetOwner()->HasLocalNetOwner())
+	{
+		if (GetOwner()->HasAuthority())
+		{
+			ActionBehaviourReplicationStart(Behaviour, EventType, Value);
+		}
+		else
+		{
+			// RPC
+			ServerRequestActionBehaviourReplicationStartRpc(Behaviour, EventType, Value);
+		}
+	}
+	else
+	{
+		UE_LOGFMT(Toolkit, Error,
+				  "Interaction: Trying to replicate HoverBehaviour {HoverBehaviour} in InteractionComponent "
+				  "{InteractionComponent} which has no local net owner!",
+				  Behaviour->GetName(), this->GetName());
+	}
+}
+
+void UUBaseInteractionComponent::ActionBehaviourReplicationStart(UActionBehaviour* Behaviour,
+																 const EInteractionEventType EventType,
+																 const FInputActionValue& Value)
+{
+	if (!Behaviour->bExecuteOnServer) // this should never happen
+	{
+		UE_LOGFMT(Toolkit, Error,
+				  "Interaction: Trying to execute ActionBehaviour {ActionBehaviour} in InteractionComponent "
+				  "{InteractionComponent} on the server, but bExecuteOnServer is false!",
+				  Behaviour->GetName(), this->GetName());
+		return;
+	}
+
+	// If desired, multicast to all clients (including originator)
+	if (Behaviour->bExecuteOnAllClients)
+	{
+		MulticastActionBehaviourReplicationStartRpc(Behaviour, EventType, Value);
+	}
+	else
+	{
+		// Execute the behaviour only on the server
+		Behaviour->OnActionEventEvent.Broadcast(this, EventType, Value);
+	}
+}
+
+void UUBaseInteractionComponent::ServerRequestActionBehaviourReplicationStartRpc_Implementation(
+	UActionBehaviour* Behaviour, const EInteractionEventType EventType, const FInputActionValue& Value)
+{
+	ActionBehaviourReplicationStart(Behaviour, EventType, Value);
+}
+
+void UUBaseInteractionComponent::MulticastActionBehaviourReplicationStartRpc_Implementation(
+	UActionBehaviour* Behaviour, const EInteractionEventType EventType, const FInputActionValue& Value)
+{
+	Behaviour->OnActionEventEvent.Broadcast(this, EventType, Value);
+}
diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h
index 755d876dc3205b0538f6bc394352def55b2da670..96774874b6e7282348655c04f7f8256b69f7747d 100644
--- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h
+++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/ActionBehaviour.h
@@ -3,21 +3,18 @@
 #pragma once
 
 #include "CoreMinimal.h"
-#include "InputAction.h"
+#include "BaseBehaviour.h"
 #include "Components/SceneComponent.h"
 #include "InputActionValue.h"
-#include "InteractionBitSet.h"
 #include "ActionBehaviour.generated.h"
 
+enum EInteractionEventType : uint8;
 
-DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnActionBegin, USceneComponent*, TriggeredComponent,
-											   const UInputAction*, InputAction, const FInputActionValue&, Value);
-
-DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnActionEnd, USceneComponent*, TriggeredComponent, const UInputAction*,
-											   InputAction, const FInputActionValue&, Value);
+DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnActionBegin, USceneComponent*, TriggerComponent,
+											   const EInteractionEventType, EventType, const FInputActionValue&, Value);
 
 UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
-class RWTHVRTOOLKIT_API UActionBehaviour : public USceneComponent
+class RWTHVRTOOLKIT_API UActionBehaviour : public UBaseBehaviour
 {
 	GENERATED_BODY()
 
@@ -26,22 +23,12 @@ public:
 	UActionBehaviour();
 
 	UPROPERTY(BlueprintAssignable)
-	FOnActionBegin OnActionBeginEvent;
-
-	UPROPERTY(BlueprintAssignable)
-	FOnActionEnd OnActionEndEvent;
+	FOnActionBegin OnActionEventEvent;
 
 protected:
 	UFUNCTION()
-	virtual void OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction,
+	virtual void OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType,
 							   const FInputActionValue& Value);
 
-	UFUNCTION()
-	virtual void OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction,
-							 const FInputActionValue& Value);
-
 	virtual void BeginPlay() override;
-
-	virtual void TickComponent(float DeltaTime, ELevelTick TickType,
-							   FActorComponentTickFunction* ThisTickFunction) override;
 };
diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/BaseBehaviour.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/BaseBehaviour.h
new file mode 100644
index 0000000000000000000000000000000000000000..d1f379bb9431749786d2ac871d119e82f5bea46e
--- /dev/null
+++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/BaseBehaviour.h
@@ -0,0 +1,34 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Components/SceneComponent.h"
+#include "BaseBehaviour.generated.h"
+
+
+UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
+class RWTHVRTOOLKIT_API UBaseBehaviour : public USceneComponent
+{
+	GENERATED_BODY()
+
+public:
+	/**
+	 * Replication part
+	 */
+
+	/**
+	 * Defines whether this behaviour will be executed on the server instead of the local client.
+	 * If set to true, an RPC is sent to the server and it will not be run locally.
+	 * */
+	UPROPERTY(EditAnywhere, BlueprintReadWrite)
+	bool bExecuteOnServer = false;
+
+	/**
+	 * Defines whether this behaviour will be executed on all connected clients that are relevant, including the
+	 * local originator client. This only has an effect if bExecuteOnServer is true, as only the server can multicast
+	 * to all other clients.
+	 */
+	UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (EditCondition = "bExecuteOnServer"))
+	bool bExecuteOnAllClients = false;
+};
diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h
index d10affdff4aa25e2e4c22818219375580f342c8f..586477d774f0599d1638df6bb256f50cac4788dd 100644
--- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h
+++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/GrabBehavior.h
@@ -23,11 +23,8 @@ public:
 	UPROPERTY(EditAnywhere, Category = "Grabbing")
 	bool bBlockOtherInteractionsWhileGrabbed = true;
 
-	virtual void OnActionStart(USceneComponent* TriggeredComponent, const UInputAction* InputAction,
+	virtual void OnActionEvent(USceneComponent* TriggerComponent, const EInteractionEventType EventType,
 							   const FInputActionValue& Value) override;
-	virtual void OnActionEnd(USceneComponent* TriggeredComponent, const UInputAction* InputAction,
-							 const FInputActionValue& Value) override;
-
 
 	/**
 	 * Called after the object was successfully attached to the hand
@@ -61,6 +58,10 @@ private:
 	 */
 	bool TryRelease();
 
+	void StartGrab(USceneComponent* TriggerComponent);
+
+	void EndGrab(USceneComponent* TriggerComponent);
+
 	bool bObjectGrabbed = false;
 
 	bool bWasSimulatingPhysics;
diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h
index 7557653fbf02dc8d1780bbb32804c583324281ad..3571440f96bf16d20eb976d5fa359bb4d5d43b4a 100644
--- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h
+++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/HoverBehaviour.h
@@ -3,16 +3,16 @@
 #pragma once
 
 #include "CoreMinimal.h"
+#include "BaseBehaviour.h"
+#include "InteractionEventType.h"
 #include "Components/SceneComponent.h"
 #include "HoverBehaviour.generated.h"
 
-
-DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnHoverStart, const USceneComponent*, TriggeredComponent, FHitResult,
-											 Hit);
-DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnHoverEnd, const USceneComponent*, TriggeredComponent);
+DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOnHoverEvent, const USceneComponent*, TriggeredComponent,
+											   const EInteractionEventType, EventType, const FHitResult&, Hit);
 
 UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
-class RWTHVRTOOLKIT_API UHoverBehaviour : public USceneComponent
+class RWTHVRTOOLKIT_API UHoverBehaviour : public UBaseBehaviour
 {
 	GENERATED_BODY()
 
@@ -22,16 +22,12 @@ public:
 	 * VRPawn) Hit: Hit Result of the trace to get access to e.g. contact point/normals etc.
 	 */
 	UPROPERTY(BlueprintAssignable)
-	FOnHoverStart OnHoverStartEvent;
-
-	UPROPERTY(BlueprintAssignable)
-	FOnHoverEnd OnHoverEndEvent;
+	FOnHoverEvent OnHoverEventEvent;
 
 protected:
 	UFUNCTION()
-	virtual void OnHoverStart(const USceneComponent* TriggeredComponent, FHitResult Hit);
-	UFUNCTION()
-	virtual void OnHoverEnd(const USceneComponent* TriggeredComponent);
+	virtual void OnHoverEvent(const USceneComponent* TriggerComponent, EInteractionEventType EventType,
+							  const FHitResult& Hit);
 
 	virtual void BeginPlay() override;
 };
diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractableComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractableComponent.h
index da85ad8b003814098fc21e112de423a097a80535..ee5c925abf5db5f6046f1740274aac26cd9daf84 100644
--- a/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractableComponent.h
+++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractableComponent.h
@@ -9,6 +9,8 @@
 #include "InteractableComponent.generated.h"
 
 
+enum EInteractionEventType : uint8;
+class UBaseBehaviour;
 struct FInputActionValue;
 class UActionBehaviour;
 class UHoverBehaviour;
@@ -53,7 +55,7 @@ public:
 	bool bAllowInteractionFromChildGeometry = true;
 
 	UFUNCTION(BlueprintCallable)
-	FORCEINLINE bool HasInteractionTypeFlag(EInteractorType type) { return type & InteractorFilter; }
+	FORCEINLINE bool HasInteractionTypeFlag(EInteractorType type);
 
 	/**
 	 * @brief Restrict interactability to given components (e.g. if an object is grabbed, block interactions from other
@@ -74,12 +76,10 @@ protected:
 	virtual void BeginPlay() override;
 
 public:
-	void HandleOnHoverStartEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor);
-	void HandleOnHoverEndEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor);
-	void HandleOnActionStartEvents(USceneComponent* TriggerComponent, const UInputAction* InputAction,
-								   const FInputActionValue& Value, const EInteractorType Interactor);
-	void HandleOnActionEndEvents(USceneComponent* TriggerComponent, const UInputAction* InputAction,
-								 const FInputActionValue& Value, const EInteractorType Interactor);
+	void HandleOnHoverEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor,
+							 const EInteractionEventType EventType);
+	void HandleOnActionEvents(USceneComponent* TriggerComponent, const EInteractorType Interactor,
+							  const EInteractionEventType EventType, const FInputActionValue& Value);
 
 	/**
 	 * @brief If hover and action behaviors are not explicitly specified, load all existing ones
diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractionEventType.h b/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractionEventType.h
new file mode 100644
index 0000000000000000000000000000000000000000..9e24c211276613c6ea0e7b11037d59e05caefba0
--- /dev/null
+++ b/Source/RWTHVRToolkit/Public/Interaction/Interactables/InteractionEventType.h
@@ -0,0 +1,8 @@
+#pragma once
+
+UENUM(BlueprintType)
+enum EInteractionEventType : uint8
+{
+	InteractionStart = 0,
+	InteractionEnd = 1,
+};
diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactors/DirectInteractionComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactors/DirectInteractionComponent.h
index 69ef74ff844a727c11a333047a7f011a12d01241..46d2d160358d991a70e232bce73bc6c9a57c52ef 100644
--- a/Source/RWTHVRToolkit/Public/Interaction/Interactors/DirectInteractionComponent.h
+++ b/Source/RWTHVRToolkit/Public/Interaction/Interactors/DirectInteractionComponent.h
@@ -3,13 +3,13 @@
 #pragma once
 
 #include "CoreMinimal.h"
+#include "UBaseInteractionComponent.h"
 #include "Components/SceneComponent.h"
 #include "Interaction/Interactables/InteractableComponent.h"
-#include "Pawn/InputExtensionInterface.h"
 #include "DirectInteractionComponent.generated.h"
 
 UCLASS(Abstract, Blueprintable)
-class RWTHVRTOOLKIT_API UDirectInteractionComponent : public USceneComponent, public IInputExtensionInterface
+class RWTHVRTOOLKIT_API UDirectInteractionComponent : public UUBaseInteractionComponent
 {
 	GENERATED_BODY()
 
@@ -20,29 +20,18 @@ public:
 	virtual void TickComponent(float DeltaTime, ELevelTick TickType,
 							   FActorComponentTickFunction* ThisTickFunction) override;
 
-	UPROPERTY(EditAnywhere, Category = "Input")
-	class UInputAction* InteractionInputAction;
-
 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Direct Interaction")
 	float InteractionSphereRadius = 15.0;
 
-	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Direct Interaction")
-	bool bShowDebugTrace = false;
-
 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Direct Interaction")
 	bool bOnlyInteractWithClosestActor = false;
 
-	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Direct Interaction")
-	TArray<AActor*> ActorsToIgnore;
-
 	virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override;
 
 private:
-	UFUNCTION()
-	void OnBeginInteraction(const FInputActionValue& Value);
+	virtual void OnBeginInteractionInputAction(const FInputActionValue& Value) override;
 
-	UFUNCTION()
-	void OnEndInteraction(const FInputActionValue& Value);
+	virtual void OnEndInteractionInputAction(const FInputActionValue& Value) override;
 
 	UPROPERTY()
 	TArray<UInteractableComponent*> PreviousInteractableComponentsInRange;
diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactors/RaycastInteractionComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactors/RaycastInteractionComponent.h
index f5c93123e8ba136e747796825f93309b285109b0..31441d516c5a34923c2ca6d26665a50a9f6069ed 100644
--- a/Source/RWTHVRToolkit/Public/Interaction/Interactors/RaycastInteractionComponent.h
+++ b/Source/RWTHVRToolkit/Public/Interaction/Interactors/RaycastInteractionComponent.h
@@ -2,16 +2,15 @@
 
 #pragma once
 
-#include <Pawn/InputExtensionInterface.h>
-
 #include "CoreMinimal.h"
+#include "UBaseInteractionComponent.h"
 #include "Components/SceneComponent.h"
 #include "Interaction/Interactables/InteractableComponent.h"
 #include "RaycastInteractionComponent.generated.h"
 
 
 UCLASS(Abstract, Blueprintable)
-class RWTHVRTOOLKIT_API URaycastInteractionComponent : public USceneComponent, public IInputExtensionInterface
+class RWTHVRTOOLKIT_API URaycastInteractionComponent : public UUBaseInteractionComponent
 {
 	GENERATED_BODY()
 
@@ -23,20 +22,13 @@ public:
 	virtual void TickComponent(float DeltaTime, ELevelTick TickType,
 							   FActorComponentTickFunction* ThisTickFunction) override;
 
-	UPROPERTY(EditAnywhere, Category = "Input")
-	class UInputAction* InteractionInputAction;
-
 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Raycast")
 	float TraceLength = 3000.0;
-	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Raycast")
-	bool bShowDebugTrace = false;
 
 private:
-	UFUNCTION()
-	void OnBeginInteraction(const FInputActionValue& Value);
+	virtual void OnBeginInteractionInputAction(const FInputActionValue& Value) override;
 
-	UFUNCTION()
-	void OnEndInteraction(const FInputActionValue& Value);
+	virtual void OnEndInteractionInputAction(const FInputActionValue& Value) override;
 
 public:
 	virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override;
diff --git a/Source/RWTHVRToolkit/Public/Interaction/Interactors/UBaseInteractionComponent.h b/Source/RWTHVRToolkit/Public/Interaction/Interactors/UBaseInteractionComponent.h
new file mode 100644
index 0000000000000000000000000000000000000000..327609c8229758f07531f96c59a67e8cb08b56d6
--- /dev/null
+++ b/Source/RWTHVRToolkit/Public/Interaction/Interactors/UBaseInteractionComponent.h
@@ -0,0 +1,74 @@
+// Fill out your copyright notice in the Description page of Project Settings.
+
+#pragma once
+
+#include "CoreMinimal.h"
+#include "Components/SceneComponent.h"
+#include "Pawn/InputExtensionInterface.h"
+#include "UBaseInteractionComponent.generated.h"
+
+
+struct FInputActionValue;
+enum EInteractionEventType : uint8;
+class UActionBehaviour;
+class UHoverBehaviour;
+
+UCLASS(ClassGroup = (Custom), meta = (BlueprintSpawnableComponent))
+class RWTHVRTOOLKIT_API UUBaseInteractionComponent : public USceneComponent, public IInputExtensionInterface
+{
+	GENERATED_BODY()
+
+public:
+	// Sets default values for this component's properties
+	UUBaseInteractionComponent();
+
+	virtual void SetupPlayerInput(UInputComponent* PlayerInputComponent) override;
+
+	UPROPERTY(EditAnywhere, Category = "Input")
+	class UInputAction* InteractionInputAction;
+
+	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Base Interaction")
+	TArray<AActor*> ActorsToIgnore;
+
+	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Base Interaction")
+	bool bShowDebugTrace = false;
+
+	/*
+	 * Replication part
+	 */
+
+	void RequestHoverBehaviourReplicationStart(UHoverBehaviour* Behaviour, const EInteractionEventType EventType,
+											   const FHitResult& Hit);
+	void HoverBehaviourReplicationStart(UHoverBehaviour* Behaviour, const EInteractionEventType EventType,
+										const FHitResult& Hit);
+
+	void RequestActionBehaviourReplicationStart(UActionBehaviour* Behaviour, const EInteractionEventType EventType,
+												const FInputActionValue& Value);
+	void ActionBehaviourReplicationStart(UActionBehaviour* Behaviour, const EInteractionEventType EventType,
+										 const FInputActionValue& Value);
+
+	// RPCs
+	UFUNCTION(Server, Reliable)
+	void ServerRequestHoverBehaviourReplicationStartRpc(UHoverBehaviour* Behaviour,
+														const EInteractionEventType EventType, const FHitResult& Hit);
+
+	UFUNCTION(NetMulticast, Reliable)
+	void MulticastHoverBehaviourReplicationStartRpc(UHoverBehaviour* Behaviour, const EInteractionEventType EventType,
+													const FHitResult& Hit);
+
+	UFUNCTION(Server, Reliable)
+	void ServerRequestActionBehaviourReplicationStartRpc(UActionBehaviour* Behaviour,
+														 const EInteractionEventType EventType,
+														 const FInputActionValue& Value);
+
+	UFUNCTION(NetMulticast, Reliable)
+	void MulticastActionBehaviourReplicationStartRpc(UActionBehaviour* Behaviour, const EInteractionEventType EventType,
+													 const FInputActionValue& Value);
+
+private:
+	UFUNCTION()
+	virtual void OnBeginInteractionInputAction(const FInputActionValue& Value);
+
+	UFUNCTION()
+	virtual void OnEndInteractionInputAction(const FInputActionValue& Value);
+};