Object moving to incorrect location after detaching from actor on Network

I’m working on a third-person shooter game where the victory condition is being the first team to complete a round of golf. I’ve worked out the controls for the golf portion where the player walks up to the ball and presses the Engage button to aim (E on keyboard, X on xbox gamepad if that matters), again to raise the golf club, and then once more to swing and hit the ball. While the ball is being aimed, it’s attached to the end of the golf club via Attach Actor To Component. (Blueprint section shown below)

Assuming the player isn’t the host, a custom event is fired to make sure the Server replicates the action. When the player goes to raise the golf club, the ball is detached and told to keep its current world location and rotation, as that’s the direction it’ll be hit in. When running standalone or as a host, this works as intended. However, when run as a client, the ball is teleported to a different location after being detached from the player’s golf club. I’ve uploaded a video showing what I mean.

From my current attempts at debugging, I've discovered that, while the ball shows itself as in the correct location for the client, and this correct position is replicated for the host and any other number of clients I add, the server itself is registering the ball in a totally different location.

Replication take time. Position on server is always off. In your case I think it will be good thing to do:

  • attach to the slot on the weapon. (attach is relative - so you nulfile it)
  • make animation on client (even fake) but effect take from server and all calculation from it. Bomb have travel time so there will be a time to sync.

Ok I’ve found a solution. I feel like it wastes resources but if I don’t do it exactly as I show it, it doesn’t work so it’s probably all necessary. Anyway, at the end of my Event Tick code, I added the following:

348337-screenshot-2021-09-09-230422.png

After defining Ball Slot Transform on the client side and then force updating the server to match instead of replicating the variable, I add the following to the end of the Golf Swing code shown above:

I also moved bBombAttached so that it goes false before I run Detach From Actor.

If you’re having similar issues, here’s what’s happening here, as I understand it:
The client knows where the component I attach the actor to is SUPPOSED to be, and since I snap the actor to that target, they share the same location and rotation. So if I want to be sure the Server puts the ball where the client wants it when the client is no longer constantly updating its location (while its attached), I need to store that parent component’s location and rotation info (Ball Slot on my golf club Blueprint) as a Transform variable.

When I tried replicating the variable normally, the server would, on occasion, update the client instead of the other way around. But whether you replicate a variable or not, the server knows you should have that variable, so if you tell the Server to use your copy, it will. The problem is, though, that once you stop, the server goes right back to setting that variable to what it thinks it should be. That’s the drawback of not replicating it, but the code does not work as otherwise shown if I replicate the transform variable, so that part was necessary.

So, as soon as the “Bomb Attached” variable goes false, I have the client run the code one last time with the Do Once node. However, we don’t want the Ball Slot transform data this time, we want whatever the transform data was from the previous tick. Done the other way, there’s about a 50/50 chance the server puts the location it has the Ball Slot component at instead of continuing to read the client data, which results in things looking like that video linked in the original post.

Finally, at the point the ball is detached, I add a 0.01-second delay which gives the server time to catch up with you, and then I run Set Actor Location and Rotation based on the transform data from the last Tick the variable was updated on the client.

This took a LOT of trial and error, attempts to simplify it make it either not work at all or only work sometimes, but when done exactly like this, it runs as intended. You don’t even see the ball appearing onscreen incorrectly as its stored on the server, it just appears where it’s supposed to. The other benefit is, since the last bit is just server code, if you run it as the Host or in a standalone game, the results either don’t run or don’t show up on screen for anyone, so it doesn’t break singleplayer either.