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.