Critical Issue: Server RPCs Dropping for Server-Owned Weapon Actors in Unreal Engine Multiplayer

Issue: In my project, server RPCs initiated by a client on server-owned actors (such as weapons) are being dropped (see image under). This setup is critical, as the weapons are created on the server, attached to clients, but remain server-owned. Since many multiplayer games also use server-owned weapon actors, I’m looking for a solution that allows client-initiated server RPCs to work on server-owned actors without them being dropped (there must be a way of doing that).


Question: How can I reliably send server RPCs from clients on server-owned actors (like weapons attached to players) while maintaining the current architecture? What strategies or best practices exist for handling this in Unreal Engine multiplayer games?


Image from Unreal docs:

Thank you for any insights!

All you need is to make the client the owner of his weapon on the server (using SetOwner function).

Initially, the client only owns its player controller. Then the controller becomes the owner of the Pawn.
Initially, the client can only send RPCs to the server from these actors.

To allow the client to send RPCs to the server via a weapon - make the client the owner of the weapon on the server.

I think I’ll change my way of doing things and Spawn the weapon in the Player Controller, so they are accessible from the Server and client who spawned it ONLY while still remaining server owned…

An actor cannot have two owners. Owner is a replicated variable, and it can only have one value, and it is thanks to it that the engine “understands” who can send RPCs and who cannot.

It doesn’t matter where you write the weapon spawning logic. The main thing is that it is called on the server, and then the right client becomes the owner of this weapon.

So Weapon Setup should happen on the Server, then at the end of the setup, I should be setting the owner to the according player controller? Is this correct?

That’s all correct.

1 Like

It still doesn’t seem to work, here’s my setup:

Server Side Function:

APlayerController* PC = GetController() ? Cast<APlayerController>(GetController()) : nullptr;

Smt* WeaponActor = GetWorld()->SpawnActor<Smt>(WeaponClass, ActorSpawnParams);

WeaponActor->SetOwner(PC)

Tho, whenever I try to call a Server RPC from client to Server, it still doesn’t do anything (I currently have a print statement to test this out…)


Doing this in the Weapon Trigger Function on the Client also make it crash (GetOwner is nullptr somehow…)

UE_LOG(LogTemp, Log, TEXT("Weapon Owner: %s"), *this->GetOwner()->GetName());

“PC” - is it a client controller? Can you display its name on the server? If it is a listening server, the index should be 1 or more.

In addition, the value may (usually) not change immediately on the client. There is a delay (several ticks) until it arrives.

PC should be the Player Controller that is going with the Player Character we are in… (I’m not sure if it actually works since it’s a server only function…)

It’s fine if it’s few frame late but it should AT LEAST trigger…

it prints, PlayerController_0 (server), PlayerController_1 (client 1), PlayerController_2 (client 2)

That’s right. :face_with_monocle:
Is replication enabled in the weapon itself?

1 Like

It is

bReplicates = true;

Yes… :thinking:
The weapons appear visually on the clients right?

1 Like

Yes they all appear correctly, I set the static mesh on the server and use Rep Notify for all the properties

Thx for your time

I made this test spawner and actor - the log is displayed… :roll_eyes:
Try doing the same for yourself, and add a spawner to the level.

Spawner:

void ASpawner::BeginPlay()
{
	Super::BeginPlay();

	if (UKismetSystemLibrary::IsServer(this))
	{
		FTimerHandle Handle;
		
		GetWorld()->GetTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda([this]() 
			{
				APlayerController* PC = UGameplayStatics::GetPlayerController(this, 1) ? Cast<APlayerController>(UGameplayStatics::GetPlayerController(this, 1)) : nullptr;


				AMyActor* MyActor = GetWorld()->SpawnActor<AMyActor>(AMyActor::StaticClass(), FActorSpawnParameters());

				MyActor->SetOwner(PC);
			}), 5, false);
	}
	
}

Actor:

AMyActor::AMyActor()
{
	PrimaryActorTick.bCanEverTick = true;
	bReplicates = true;
}
void AMyActor::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	if (GetOwner()
		&& !UKismetSystemLibrary::IsServer(this))
	{
		UE_LOG(LogTemp, Warning, TEXT("OwnerValidFromClient"));

	}
}
1 Like

I’ll test it tomorrow and will let you know!

I’ve just realized that there’s a warning in the logs that is pretty interesting about the problem:

LogNet: Warning: UNetDriver::ProcessRemoteFunction: No owning connection for actor CustomWeapon_1. Function ServerAddNiagaraComponent will not be processed

This also doesn’t do anything…

I found this about the topic but not sure how he does it:
https://www.youtube.com/watch?v=eF0qWffFw9U&ab_channel=enigmatutorials

This means that when you call SetOwner, the new owner is set to a different controller than the one you then try to call the server event from.
Can you add GetOwner name to the output log in the weapon tick function if Owner or other message if owner in not valid?

1 Like

Before doing SetOwner(), GetOwner is nullptr, after setting it, it’s PlayerController_1.

You just said that the new owner is set to a different controller than before, but is the server PlayerController and the client PlayerController the same? (Is getting the PC on the server the problem?)