For client to server RPCs, the Owner must be the client attempting to call the RPC.
In general, actors that are placed in a level (such as doors) are never owned by any particular player.
I see you are attempting to SetOwner from the Interact function, but the Owner cannot be set from client-side, it has to be set from Authority, so it has no effect there. It may appear to work (as in, it you print owner after calling SetOwner it will show) but since the Owner is not modified on server-side it will not accept the RPC.
Instead of trying to modify the Owner of level-placed actors, you should rework your code a bit to route the RPCs through a real player-owned class (such as Character), then forward to actor code once you’re on server side.
You can adjust your code pretty easily to do so.
AInteractiveActor does not do the RPC, it just needs an entry point
UFUNCTION()
virtual void OnInteract(APawn* Sender)
{
if (HasAuthority())
{
// server interaction
}
else
{
// client interaction (optional - can be used for prediction)
}
}
Move the RPC logic to your Character class instead
UFUNCTION(Server, Reliable, WithValidation)
virtual void ServerInteract(AInteractiveActor* Target);
virtual void ServerInteract_Implementation(AInteractiveActor* Target);
virtual void ServerInteract_Validate(AInteractiveActor* Target);
// in function Interact()
if (Hit.GetActor()->IsA(AInteractiveActor::StaticClass()))
{
AInteractiveActor* InteractiveActor = Cast<AInteractiveActor>(Hit.GetActor());
if (!HasAuthority())
InteractiveActor->OnInteract(this); //optional, can be used for prediction
ServerInteract(InteractiveActor);
}
// end function Interact
void AEscapeGameCharacter::ServerInteract_Implementation(AInteractiveActor* Target)
{
if (Target)
Target->OnInteract(this);
}
This answer was written by Chatouille from UE5 official forum. Thanks to him for his help.
Final character.h
// Handle all interact action from player
void Interact();
UFUNCTION(Server, Reliable)
void ServerInteract(AInteractiveActor* Target);
void ServerInteract_Implementation(AInteractiveActor* Target);
Final character.cpp
void AEscapeGameCharacter::Interact()
{
UE_LOG(LogTemp, Warning, TEXT("AEscapeGameCharacter::Interact"));
FVector StartLocation = FirstPersonCameraComponent->GetComponentLocation();
FVector EndLocation = FirstPersonCameraComponent->GetForwardVector() * 200 + StartLocation;
FHitResult Hit;
GetWorld()->LineTraceSingleByChannel(Hit, StartLocation, EndLocation, ECC_Visibility);
DrawDebugLine(GetWorld(), StartLocation, EndLocation, FColor::Red, false, 5, 0, 5);
if (!Hit.bBlockingHit) { return; }
if (Hit.GetActor()->IsA(AInteractiveActor::StaticClass()))
{
AInteractiveActor* InteractiveActor = Cast<AInteractiveActor>(Hit.GetActor());
UE_LOG(LogTemp, Warning, TEXT("Net Rep Responsible Owner: %s"), (HasNetOwner() ? TEXT("Yes") : TEXT("No")));
ServerInteract(InteractiveActor);
} else
{
UE_LOG(LogTemp, Warning, TEXT("Hitted something"));
}
}
void AEscapeGameCharacter::ServerInteract_Implementation(AInteractiveActor* Target)
{
if (Target)
{
Target->OnInteract(this);
}
}
Final InteractiveActor.h
UFUNCTION(BlueprintCallable)
virtual void OnInteract(APawn* Sender);
Final Door.h
// Handle interaction
virtual void OnInteract(APawn* Sender) override;
virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;
UFUNCTION()
bool ToggleDoor();
UFUNCTION()
void OnRep_bOpen();
Final Door.cpp
bool ADoor::ToggleDoor()
{
UE_LOG(LogTemp, Warning, TEXT("ToggleDoor : This actor has %s"), ( HasAuthority() ? TEXT("authority") : TEXT("no authority") ));
bOpen = !bOpen;
if (HasAuthority())
{
// if bOpen changed to true play opening animation if it changed to false play closing animation.
DoorSkeletalMesh->PlayAnimation(bOpen ? DoorOpening_Animation : DoorClosing_Animation, false);
}
return bOpen;
}
void ADoor::OnRep_bOpen()
{
UE_LOG(LogTemp, Warning, TEXT("OnRep_bOpen Has authority : %s"), (HasAuthority() ? TEXT("yes") : TEXT("no")));
UE_LOG(LogTemp, Warning, TEXT("bOpen : %s"), (bOpen ? TEXT("yes") : TEXT("no")));
// if bOpen changed to true play opening animation if it changed to false play closing animation.
DoorSkeletalMesh->PlayAnimation(bOpen ? DoorOpening_Animation : DoorClosing_Animation, false);
}
void ADoor::OnInteract(APawn* Sender)
{
Super::OnInteract(Sender);
UE_LOG(LogTemp, Warning, TEXT("Server : ServerInteract"));
ToggleDoor();
}
void ADoor::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ADoor, bOpen);
}