Move character and unpossess in same frame

I’m having an issue where based on some event, I want to move the currently possessed character and then have the controller possess another pawn. This needs to work in a networked environment.

For simplicity’s sake, let’s assume that I’ve bound an input in my character to a function, which in turn calls a Server RPC. In the Server RPC, I call:

SetActorLocation(FVector(-18000, -12375, 145));
GetController()->UnPossess();

What I’ve noticed is that if I comment out the UnPossess call, this works fine. But if I include the UnPossess call, the movement happens on the server but is not replicated on the client.

In case you’re wondering: Yes, the character is marked as both bReplicates = true and bAlwaysRelevant = true. Character movement also works fine in a networked setting for me all the time for all players except for once the character is unpossessed.

So I dug into this some more and here’s what I found:

  • If I first move the actor, then set a timer for 0.01s and do the unpossession when the timer goes off, everything works fine. It just seems to be a problem if it happens in the same frame.
  • If I don’t include the unpossess call, then the movement eventually leads to UCharacterMovementComponent::ClientAdjustPosition_Implementation being called.
  • If I don’t include the unpossess call, it never reaches that point because APlayerController::SendClientAdjustment() returns early since AcknowledgedPawn != GetPawn()
  • If I don’t include the unpossess call, it does seem to replicate ReplicatedMovement and it hits a breakpoint in ACharacter::OnRep_ReplicatedMovement, however this leads to ACharacter::PostNetReceiveLocationAndRotation() where it doesn’t end up updating the location because ReplicatedBasedMovement.HasRelativeLocation() is true.

At this point, I’m out of ideas. I don’t really want to spend more time digging into engine network movement code. I’ll use the timer workaround if I have to, but I’d rather figure out if this is a bug or a misunderstanding on my side.

In classic rubber duck fashion, writing out the details of the question lead to me figuring out better things to google. It turns out setting GetCharacterMovement()->bRunPhysicsWithNoController = true; on my character was sufficient to make everything work as expected.

It’s a little bit weird since I’m not using physics, but whatever makes it work I guess.