Bizarre & Inconsistent StreamingEnabled Behavior with Player-Controlled Drone - How to Fix?

My game recently had a plane added to it where it has a two-player crew; one is the pilot, one launches and controls a player-guided missile.

With StreamingEnabled, this system would be an issue (the player guides the missile out of streaming range), but I got around this by having the missile be a model with the “Persistent” streaming mode, the primary part of the model is made the primary part of the controlling player’s character (and then resets when the player exits the seat or detonates the missile), making that primary part have its network owner set to the controlling player, and all is well. I’ve used it for other missile systems in my game, and it works flawlessly.

However, this one doesn’t. If the co-pilot launches a missile mid-flight, and the missile gets out of streaming range, the out of streaming range screen appears, as you can see below:


This is obviously a big issue given that its not that difficult to get out of streaming range controlling a missile while the jet with the rest of your character in it goes the other direction.

However, if the co-pilot is by themself, they can fly the missile for unlimited range, no issue.

The co-pilot can even fly this missile with unlimited range even if there’s a pilot - as long as pilot has not taken off yet.

I do not understand this behavior, at all. I’ve tried a lot of hacky ways around it, like resetting the missile’s NetworkOwnership (setting it to nil and not auto) before the missile has it set to the controlling player, but nothing seems to work.

I have a Battleship in the game where you oftentimes have a captain of the ship controlling the ship and a separate player launching and guiding cruise missiles from there, and there is no issue with the exact same system that is used for this plane’s air-launched cruise missile - except the battleship’s network ownership is set to nil and never to the captain player (movement is done on the server via a VehicleSeat - not optimal but the only workaround here)

I assume the issue here is something to do network ownership disputes - even though the way I’ve coded it, this should not be an issue (in theory). How can I fix this/get around it?

3 Likes

Set the co-pilots replication focus at the missile while they’re guiding it, then switch back when they release control:

-- in a server script
local function giveMissileControl(player, missile)
    missile.PrimaryPart:SetNetworkOwner(player)

    -- stream the area around the missile
    player.ReplicationFocus = missile.PrimaryPart
    player:RequestStreamAroundAsync(missile.PrimaryPart.Position) -- optional, forces a head-start
end

local function releaseControl(player)
    player.ReplicationFocus = nil -- reverts to the character
end

Also, I could be wrong on this one, but don’t update the missile’s velocity from the server after transferring the ownership if you’re doing that, the server steals back ownership if you do that

3 Likes

With StreamingEnabled on, any client-owned part pauses physics when it drifts outside its owners streaming radius even in a Persistent model because Roblox won’t simulate objects the client can’t reliably see; Persistent parts should simulate locally, Persistent StreamingMode part physics frozen

Your Battleship missiles work because cloning from ServerStorage at fire-time forces a fresh streaming/physics initialization; Scud models that live in Workspace from map-load never reprime and thus lock up when they drift out.
part:SetNetworkOwner(player), the parts physics run on the client but any client-owned part that moves outside of that client’s streaming radius will pause its physics even if the part is inside a Persistent model, physics will still freeze and this is actually intended to prevent missimulation when the client doesn’t exactly have the full world data.

Roblox centers streaming and physics around whichever Part you assign to player.ReplicationFocus (default = your character’s PrimaryPart) ReplicationFocus

So we either…
A) Spawn from ServerStorage
Mirroring your battleship system and moving your missile prefab into ServerStorage or ReplicatedStorage then cloning and parenting it to Workspace only when firing e.g.;

local m = ServerStorage.Missile:Clone()
m.Parent = workspace
m:SetPrimaryPartCFrame(spawnCFrame)
m.PrimaryPart:SetNetworkOwner(player)

cloning at fire-time forces the engine to reinitialize streaming/physics state, preventing freezes

Or we will…
B) Switch the ReplicationFocus to the Missile
If you need to keep the missile model in Workspace, you can instead tell Roblox’s streaming system to focus on the missile launch after you hand physics ownership of the missile to the co-pilot call;

player.ReplicationFocus = missile.PrimaryPart

this will make the missile the center of streaming and physics so it never drifts out of range.
When it explodes or the player exits, reset it with:

player.ReplicationFocus = nil

3 Likes

Some great replies above - both were the answer! But this one came first…

2 Likes

The Battleship missiles were not spawned in from ServerStorage, directly. They were launched from the Battleship model that spawns in from ServerStorage, but there could be quite a bit of time between the Battleship being spawned in and when a player goes to control its missiles.

Same with the Scud Nuclear Missile in the game.

No issue with either, where I don’t use ReplicationFocus and instead use my hacky PrimaryPart method. However, that did not work with this jet - ReplicationFocus did, but there was ONE issue with that. This issue is even more bizarre and inconsistent than the original one posted.

If the co-pilot jumps out of the seat while controlling a missile, for the server/other players it will show that the co-pilot has jumped out of the plane and could be anywhere but - but on the client’s side, it will still show them in the plane and still able to launch missiles. Usually, jumping out while controlling a missile would do a protocol on the client where the local script and associated UI are deleted and the camera is reset, and the missile would be exploded on the server, but this doesn’t happen

(I tried to get a recording of this oddity in action, but it seems to be inconsistent and I can’t seem to get a good example of it)

I’ve tried a ton of work-arounds - I made it so that when the player hops into the co-pilot seat, their avatar is set to Persistent/PersistentPerPlayer. I’ve tried locking them to their seat via disabling jumping via disabling the jump state (on first the server, which didn’t work, and then on the client) and this method; neither of them worked, the player was still able to click jump and the player would have hopped out of the seat on everyone but their own’s end.

Thank you for the clarification!
It appears that the seat bug happens because once you point your streaming/physics focus at the missile, the seat itself falls outside the clients streaming radius, the client never sees that its unoccupied and never runs its jump cleanup logic. :sad:

EDIT;

On the server, watch for the copilot seats Occupant to go nil and when that happens, reset the players ReplicationFocus back to their character, we tell the client via a RemoteEvent to tear down the missile UI, reset the camera and destroy the missile, that’d be my approach on this.

1 Like

don’t leave this open if it’s been solved @CoolJohnnyboy