Standard and cheat-resistant way to setup propagation of player action in a networked game

Hi, I’ve been working on my first multiplayer game on UE4 and need some clarifications. The scenario is that I have turret guns in the level that the player can control and shoot. I’ve set it up like so-

  • The turrent gun has a sphere trigger volume, if a player is inside it, a reference to this turret gets written on the player character class he gets a prompt like “Press E to control”
  • When the player presses the required key, they start controlling the referenced turret and can shoot with it, and by pressing the key again they can leave the turret and go back about their business as usual.

Now we’ll leave actually firing from the turret alone for now, since I understand that’s a simple matter of spawning replicated projectiles on the server; what I want for now is for my players to be able to control the turrets and that action being propagated through to all the clients.

The relevant functions are :

In the character class :



UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Gameplay")
void AttemptControlOffensiveConstruct();

bool AShooterSandboxCharacter::AttemptControlOffensiveConstruct_Validate() {
return true;
}

void AShooterSandboxCharacter::AttemptControlOffensiveConstruct_Implementation()//
{
if (currentConstructInVicinity) {
currentConstructInVicinity->ControlOffensive(myController);
}
}


and in the turret class:




UFUNCTION(Server, Reliable, WithValidation, BlueprintCallable, Category = "Offensive Construct Controls")
void ControlOffensive(class AShooterSandboxController* occupantController);

bool ABaseOffensiveConstruct::ControlOffensive_Validate(AShooterSandboxController * occupantController) {
return true;
}

void ABaseOffensiveConstruct::ControlOffensive_Implementation(AShooterSandboxController * occupantController)
{
if (occupantController == nullptr) {//!HasAuthority()||
return;
}

userController = occupantController;
userCharacter = Cast<AShooterSandboxCharacter>(occupantController->GetCharacter());

userCharacter->GetFollowCamera()->SetupAttachment(muzzle, FName("CameraHolder"));
userController->Possess(this);
}



What happens with this is that the server always ends up controlling the turret, even if it was triggered by a client. And that is understandable since I see that I’m always executing AttemptControlOffensiveConstruct() on the server. So I made it into a regular function (NOT an RPC) and thought each player would trigger the function on their local copies, and then call the ControlOffensive server function with a copy of their controller, and since that function is executing on the client, it should work? but it doesn’t. It works for the server, but not for the client. The clients do nothing, just stand there. I even studied the “Multiplayer Shootout” sample project offered by UE and thought maybe that’ll give me some ideas, but that confused me even more.

Can someone please simplify this concept for me or just nudge me in the correct direction here. How do I control the turret from a client and have all other connected players seeing the correct state. Thanks!

Anyone? Commenting to bump, still need some help with this

Sounds like you might need to do some debugging and logging to figure out what’s not being called when you expect it to be.

One way to accomplish your task would be to let the client go ahead and control the turret as he would in single player mode and then at the same time, send an RPC to the server that the client is attempting to control it. The server can verify that the client is actually colliding with your trigger sphere before allowing the character to control it on its end. If the request wasn’t valid, the server can send an RPC to the client to tell it to leave the turret. If the request was valid, the server can send a multicast RPC to tell all the other simulated proxies to get on the turret.

Another way to handle it is to have the client send a request to the server that it wants to control the turret and then wait for a response. Then the server can verify and send a multicast RPC if the request was good. This way is slightly easier to set up, but it means the client will experience a delay when trying to control the turret.

If a server RPC isn’t being called, make sure the calling client is the owner. If a multicast RPC isn’t being called, make sure the server is the one calling it.

If you’re not already using it, consider using UKismetSystemLibrary::PrintString to debug issues like this. It’ll automatically prepend which client/server is calling it and it prints to the screen. When I’m having multiplayer troubles I temporary put a few of these into functions to see what’s being called where. Super helpful.