Network owner is set to the player, but physics doesn't replicate to the server?

Hello all!

I’m currently working on a game, which involves moving & placing objects around. I’m experiencing a problem tho, where some particular objects do not replicate to the server from the client, as seen in the video below.

Whenever a player sends a signal to the server, that they wish to pick up an object, the following code is ran on the server (shortened version below):

local function PickUp(player: Player, moveableIdx: number, hitPointRelative: Vector3)
	if type(moveableIdx) ~= "number" or moveableIdx < 1 or moveableIdx > #moveables then
		return
	end
	
	if playerMoving[player.Name] and moveables[playerMoving[player.Name]] and moveables[playerMoving[player.Name]].Parent then
		return -- The player is already moving something
	end
	
	local moveable: Model = moveables[moveableIdx]
	
	if moveable:GetAttribute(globalSettings.MoveablePickedAtt) then
		return -- This object is already being moved up by someone
	end
	moveable:SetAttribute(globalSettings.MoveablePickedAtt, player.Name)
	
	helperModule.SetDescendantsCollisionGroup(moveable, "NoPlayerColl")
	
	playerMoving[player.Name] = moveableIdx
	
	local primary: BasePart = moveable.PrimaryPart
	
	for _, child: Instance in primary:GetChildren() do
		if child.Name:match(globalSettings.AttachableWeldPrefix) then
			if child:IsA("WeldConstraint") and child.Part0 and child.Part0:FindFirstChild(child.Name.."_Duplicate") then
				child.Part0[child.Name.."_Duplicate"]:Destroy() -- Destroy the ObjectValue pointing to the weld we are about to destroy
			elseif child:IsA("ObjectValue") and child.Value then
				child.Value:Destroy() -- Destroy the weld that this ObjectValue is pointing at
			end
			
			child:Destroy()
		end
	end
	
	primary.Anchored = false
	primary:SetNetworkOwner(player)

	moveableEvent:FireClient(player, globalSettings.MoveablePickedUpActionName, moveable, hitPointRelative)
end

Now I assume that I can remove most of what’s there for this purpose, since it shouldn’t matter for this case, so the shortened version is this:

local function PickUp(player: Player, moveableIdx: number, hitPointRelative: Vector3)
	local moveable: Model = moveables[moveableIdx]
	
	local primary: BasePart = moveable.PrimaryPart
	primary.Anchored = false
	primary:SetNetworkOwner(player)

	moveableEvent:FireClient(player, globalSettings.MoveablePickedUpActionName, moveable, hitPointRelative)
end

Essentially all that is going on there, is the primary part gets unanchored, in case it is anchored, and then the network owner is set to the player and a new signal is sent back to the player, telling them that they can now pick up the object.
I should also mention, that it is guaranteed, that once this code runs, before the network owner is set, every BasePart inside the moveable object is welded to the primary part and is unanchored.

Once the player receives the signal, that they’re good to go, the following code is ran on the client (and once again, a shortened version is below):


local function PickedUp(moveable: Model, hitPointRelative: Vector3)
	moving = moveable
	
	local primary: BasePart = moveable.PrimaryPart
	
	local atch: Attachment = Instance.new("Attachment")
	atch.Name = globalSettings.MoveablePrefix..atch.Name
	atch.Position = hitPointRelative
	atch.Parent = primary
	
	local alignPosition: AlignPosition = Instance.new("AlignPosition")
	alignPosition.Name = globalSettings.MoveablePrefix..alignPosition.Name
	alignPosition.Mode = Enum.PositionAlignmentMode.OneAttachment
	alignPosition.MaxForce = 10000
	alignPosition.MaxVelocity = 50
	alignPosition.Responsiveness = 50
	alignPosition.Attachment0 = atch
	alignPosition.Parent = primary
	
	local alignOrientation: AlignOrientation = nil
	if moveable:GetAttribute("Rotatable") then
		alignOrientation = Instance.new("AlignOrientation")
		alignOrientation.Name = globalSettings.MoveablePrefix..alignOrientation.Name
		alignOrientation.Mode = Enum.OrientationAlignmentMode.OneAttachment
		alignOrientation.Responsiveness = 50
		alignOrientation.MaxTorque = 50000
		alignOrientation.Attachment0 = atch
		alignOrientation.Parent = primary
	end
	
	local offset: CFrame = character.PrimaryPart.CFrame:ToObjectSpace(atch.WorldCFrame)
	offset = CFrame.new(moveableModule.MultXZUnit(offset.Position, moveable:GetAttribute("RecommendedDistance") or clientSettings.MoveableDefaultDistance)) * offset.Rotation
	offset = moveableModule.ClampMoveableOffset(offset)
	local initOffset: CFrame = offset
	
	steppedConn = RunSerivce.Stepped:Connect(function(time: number, deltaTime: number)
		if (not alignPosition and not alignOrientation) or not character or character.Humanoid.Health == 0 then
			Drop()
			return
		end
		
		offset = moveableModule.ApplyMovementInputOffset(moveable, offset, initOffset, deltaTime, rotateMode)
		
		local world: CFrame = character.PrimaryPart.CFrame:ToWorldSpace(offset)
		alignPosition.Position = world.Position
		
		if alignOrientation then
			alignOrientation.CFrame = world.Rotation
		end
		
		if moveable:GetAttribute(globalSettings.AttachableAtt) and IsAttachableToSomething(moveable) then
			moveableHighlight.OutlineColor = Color3.new(0.215686, 1, 0)
		else
			moveableHighlight.OutlineColor = Color3.new(1, 1, 1)
		end
	end)
	
	AddVisualsToMoveable(moveable, atch.WorldPosition)
end

Which I’ll shorten to this:


local function PickedUp(moveable: Model, hitPointRelative: Vector3)
	local primary: BasePart = moveable.PrimaryPart
	
	local atch: Attachment = Instance.new("Attachment")
	atch.Name = globalSettings.MoveablePrefix..atch.Name
	atch.Position = hitPointRelative -- The white point on the object in the video. Set to where the player's mouse was when they picked the object up
	atch.Parent = primary
	
	local alignPosition: AlignPosition = Instance.new("AlignPosition")
	alignPosition.Name = globalSettings.MoveablePrefix..alignPosition.Name
	alignPosition.Mode = Enum.PositionAlignmentMode.OneAttachment
	alignPosition.MaxForce = 10000
	alignPosition.MaxVelocity = 50
	alignPosition.Responsiveness = 50
	alignPosition.Attachment0 = atch
	alignPosition.Parent = primary
	
	local alignOrientation: AlignOrientation = nil
	if moveable:GetAttribute("Rotatable") then
		alignOrientation = Instance.new("AlignOrientation")
		alignOrientation.Name = globalSettings.MoveablePrefix..alignOrientation.Name
		alignOrientation.Mode = Enum.OrientationAlignmentMode.OneAttachment
		alignOrientation.Responsiveness = 50
		alignOrientation.MaxTorque = 50000
		alignOrientation.Attachment0 = atch
		alignOrientation.Parent = primary
	end
	
	local offset: CFrame = character.PrimaryPart.CFrame:ToObjectSpace(atch.WorldCFrame)
	-- some more magic being done here with the offset
	
	steppedConn = RunSerivce.Stepped:Connect(function(time: number, deltaTime: number)
		offset = moveableModule.ApplyMovementInputOffset(moveable, offset, initOffset, deltaTime, rotateMode)
		
		local world: CFrame = character.PrimaryPart.CFrame:ToWorldSpace(offset)
		alignPosition.Position = world.Position
		
		if alignOrientation then
			alignOrientation.CFrame = world.Rotation
		end
	end)
end

Essentially all that code does is it creates an attachment and AlignPosition/Orientation objects on the client, which are then moved about in front of the player every physics Step

Every time the player wishes to drop the object, it removes all local instances that it created and sends a signal to the server to let the server know, that the player dropped the object. The code for that looks like this on the server (once again, shortened version below):

local function Drop(player: Player)
	if not playerMoving[player.Name] then
		return -- The player isn't moving anything at the moment
	end
	
	local moveableIdx: number = playerMoving[player.Name]
	playerMoving[player.Name] = nil
	
	local model: Model = moveables[moveableIdx]
	model:SetAttribute(globalSettings.MoveablePickedAtt, nil)
	helperModule.SetDescendantsCollisionGroup(model, "Default")
	
	local primary: BasePart = model.PrimaryPart
	primary:SetNetworkOwnershipAuto()
	primary.AssemblyLinearVelocity = Vector3.zero
	primary.AssemblyAngularVelocity = Vector3.zero
	
	if model:GetAttribute(globalSettings.AttachableAtt) then
		local overlapParams: OverlapParams = OverlapParams.new()
		overlapParams.FilterDescendantsInstances = {charactersContainer}
		overlapParams.FilterType = Enum.RaycastFilterType.Exclude
		
		local parts: {BasePart} = workspace:GetPartBoundsInBox(primary.CFrame, primary.Size + Vector3.one*0.01, overlapParams)

		local welded: {PVInstance} = {}
		for _, part: {BasePart} in parts do
			local ancestor: PVInstance? = helperModule.FindFirstAncestorWithAttribute(part, globalSettings.AttachableToAtt, true)
			if not ancestor or table.find(welded, ancestor) or ancestor == model then
				continue
			end

			local atchPrimary: BasePart = ancestor.ClassName == "Model" and ancestor.PrimaryPart or ancestor
			local weld: WeldConstraint = helperModule.Weld(atchPrimary, primary, primary, globalSettings.AttachableWeldPrefix)
			table.insert(welded, ancestor)
			
			local weldDuplicate: ObjectValue = Instance.new("ObjectValue")
			weldDuplicate.Name = weld.Name.."_Duplicate"
			weldDuplicate.Value = weld
			weldDuplicate.Parent = atchPrimary
		end
	end
end

Which I’ll shorten to this:

local function Drop(player: Player)	
	local moveableIdx: number = playerMoving[player.Name]
	playerMoving[player.Name] = nil
	
	local model: Model = moveables[moveableIdx]
	
	local primary: BasePart = model.PrimaryPart
	primary:SetNetworkOwnershipAuto()
	primary.AssemblyLinearVelocity = Vector3.zero
	primary.AssemblyAngularVelocity = Vector3.zero
end

All that this part of code does, is it reclaims the ownership (sets it to auto, to be more exact) and sets the velocity to zero, so the object wouldn’t go crazy. But as seen in the video, the problem arises even before this slice of code, since the position wasn’t even replicating when the TV was picked up.

As seen in the video provided, the client sided code seems to function just fine, and it is guaranteed that the pick up code on the client only gets executed once the server gives the proper signal. However on the server, when picking up the TV, it does send the signal to the client and the client is able to move it by physics, which it wouldn’t be able to do if it wasn’t the network owner. But the server doesn’t seem to be replicating the position, like it does with the other objects. I have tried printing out the network owner of the TV while it’s being moved and it always says that the network owner is indeed the player.

The TV looks like this in the explorer


All the parts are welded together and connected to the primary part.

For example the boxes look like this in the explorer:


And the boxes work at the moment, however the exact same problem came up before with them too. If I were to set the primary part of the box to the meshpart and remove the previous primary part, then, once again, the same thing happens

I am more leaned towards this being a roblox bug rather than a bug in my code, since the code shouldn’t really care at all what the other parts are inside the moveable object and definitely shouldn’t break if you change the primary part to a meshpart instead of a normal part.

I haven’t unfortunately managed to reproduce this in a simpler form in a separate place.

I hope I covered everything important here. If not, please ask and I’ll provide more info.

2 Likes

Just bumping this because I don’t know what to do. I can’t find anything wrong with my code, so I’m starting to think it might be a roblox bug.

Okay, I am now basically certain that the problem is in roblox, because I’ve managed to find the root of the issue. If every BasePart inside the models that I am moving is set to Massless, then this bug starts appearing. If even one isn’t massless, then it works just fine.