Client can't call RPC Server even with a valid NetOwner.

Hi everybody, and thank you for the help.

It’s the first Actor i build for Multiplayer with replication so if you think i should have used another technique please tell me.

I’m running UE5.4 with two player and as net connection i use listen server.

So i’m trying to make a simple Door wich you can interact with to open or close it. When you interact with the door the client call a server RPC named ServerInteract, this RPC update the bOpen attribute of the door actor. This attribute is marked as ReplicatedUsing so its should be replicated on all the client and also call the OnRep function on all the client. This OnRep function play the opening or closing animation based on bOpen state.
My problem is that when i interact with the door from the client 0 wich is also the host/server the replication work perfectly. But when i interact with the client 1 wich isn’t the host but a simple client the ServerInteract RPC isn’t even call even if the owner of the actor has a valid connection.

You can see in the video a test where the logs show clearly my point.

Door.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "../InteractiveActor.h"
#include "Door.generated.h"

UCLASS()
class ESCAPEGAME_API ADoor : public AInteractiveActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor's properties
	ADoor();
	
	// Handle interaction
	virtual void ServerInteract_Implementation(APawn* InteractionSender) override;

	virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;

	UFUNCTION(BlueprintCallable)
	bool ToggleDoor();

	UFUNCTION()
	void OnRep_bOpen();

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;
	
protected:
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Door)
	UAnimSequence* DoorOpening_Animation;
	
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Door)
	UAnimSequence* DoorClosing_Animation;

	UPROPERTY(ReplicatedUsing=OnRep_bOpen ,EditAnywhere, BlueprintReadOnly, Category = Door)
	bool bOpen;



public:
	UPROPERTY(EditAnywhere, category = Door)
	USkeletalMeshComponent* DoorSkeletalMesh;

	UPROPERTY(EditAnywhere, category = Door)
	USceneComponent* DoorRoot;
	
};

Door.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "Door.h"

#include "Net/UnrealNetwork.h"

// Sets default values
ADoor::ADoor()
{
 	// 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;

    DoorRoot = CreateDefaultSubobject<USceneComponent>(TEXT("DoorRoot"));
    SetRootComponent(DoorRoot);

    DoorSkeletalMesh = CreateDefaultSubobject<USkeletalMeshComponent>("DoorMesh");
    DoorSkeletalMesh->SetupAttachment(DoorRoot);

	// Initiate variables
	bOpen = false;
	bReplicates = true;
}

// Called when the game starts or when spawned
void ADoor::BeginPlay()
{
	Super::BeginPlay();
}

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::ServerInteract_Implementation(APawn* player)
{
	Super::ServerInteract_Implementation(player);

	UE_LOG(LogTemp, Warning, TEXT("Server : ServerInteract"));
	
	ToggleDoor();
}

void ADoor::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
	Super::GetLifetimeReplicatedProps(OutLifetimeProps);

	DOREPLIFETIME(ADoor, bOpen);
}


InteractiveActor.h

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "InteractiveActor.generated.h"

UCLASS(Abstract)
class ESCAPEGAME_API AInteractiveActor : public AActor
{
	GENERATED_BODY()

public:
	// Interact function
	UFUNCTION(Server, Reliable, WithValidation)
	virtual void ServerInteract(APawn* InteractionSender);
	virtual void ServerInteract_Implementation(APawn* InteractionSender);
	virtual bool ServerInteract_Validate(APawn* InteractionSender);

};

InteractiveActor.cpp

// Fill out your copyright notice in the Description page of Project Settings.


#include "InteractiveActor.h"

void AInteractiveActor::ServerInteract_Implementation(APawn* InteractionSender)
{
	UE_LOG(LogTemp, Warning, TEXT("ServerInteract with %s, Has authority: %s"), *InteractionSender->GetName(), (HasAuthority() ? TEXT("Yes") : TEXT("No")));
}

bool AInteractiveActor::ServerInteract_Validate(APawn* InteractionSender)
{
	return true;
}

Player actor interact function wich is called when f is pressed.

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());
		
		if (AEscapeGamePlayerController* controller = Cast<AEscapeGamePlayerController>(GetController()))
		{
			UE_LOG(LogTemp, Warning, TEXT("Net Rep Responsible Owner: %s"), (HasNetOwner() ? TEXT("Yes") : TEXT("No")));
			InteractiveActor->SetOwner(this);
			InteractiveActor->ServerInteract(this);
		}
		
	} else
	{
		UE_LOG(LogTemp, Warning, TEXT("Hitted something"));
	}
	
}

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.

  1. 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)
    }
}
  1. 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);
}
1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.