-- src/ServerScriptService/DragManager.server.lua local ReplicatedStorage = game:GetService("ReplicatedStorage") local CollectionService = game:GetService("CollectionService") local Players = game:GetService("Players") local RunService = game:GetService("RunService") local DraggingConfig = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("DraggingConfig")) local DragEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("DragEvent") local DropEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("DropEvent") -- Store active dragging states local activeDrags = {} -- Validate if the object can be dragged (e.g. TreeSegments, Logs, Furniture) local function canBeDragged(part) return CollectionService:HasTag(part, "Draggable") or CollectionService:HasTag(part, "TreeSegment") end -- Cleanup function when a player drops an object or disconnects local function dropObject(player) local dragData = activeDrags[player.UserId] if dragData then -- Clean up attachments and constraints if dragData.dragAttachment then dragData.dragAttachment:Destroy() end if dragData.targetAttachment then dragData.targetAttachment:Destroy() end if dragData.alignPos then dragData.alignPos:Destroy() end if dragData.alignOri then dragData.alignOri:Destroy() end -- Reset Network Ownership to Server (or Auto) if dragData.part and dragData.part:IsDescendantOf(workspace) then -- Optional: Add a small impulse so it doesn't just freeze mid-air pcall(function() dragData.part:SetNetworkOwner(nil) end) end activeDrags[player.UserId] = nil end end -- Handle Drag Event Request local function onDragRequest(player, targetPart, hitPoint) if not targetPart or not targetPart:IsA("BasePart") then return end if not canBeDragged(targetPart) then return end local char = player.Character if not char or not char:FindFirstChild("HumanoidRootPart") then return end -- Distance verification local distance = (char.HumanoidRootPart.Position - targetPart.Position).Magnitude if distance > DraggingConfig.MaxGrabDistance then return end -- Ensure player isn't already dragging something if activeDrags[player.UserId] then dropObject(player) end -- Verify the part isn't already being dragged by someone else for _, dragData in pairs(activeDrags) do if dragData.part == targetPart then return end end -- Setup Attachments and Constraints local partAttachment = Instance.new("Attachment") partAttachment.Name = "DragAttachment" partAttachment.CFrame = targetPart.CFrame:ToObjectSpace(CFrame.new(hitPoint)) partAttachment.Parent = targetPart -- We create a proxy attachment in Terrain, managed by the client later, or just update it via Server -- For Smoothest results, we give the Client network ownership and let local physics do the dragging, but the Constraints exist Server-Side. local targetAttachment = Instance.new("Attachment") targetAttachment.Name = "TargetAttachment_Player" .. player.UserId -- Setting it to character's Head/Camera relative position on the server initially targetAttachment.WorldCFrame = CFrame.new(char.Head.Position + char.Head.CFrame.LookVector * DraggingConfig.HoldDistance) targetAttachment.Parent = workspace.Terrain local alignPos = Instance.new("AlignPosition") alignPos.Attachment0 = partAttachment alignPos.Attachment1 = targetAttachment alignPos.MaxForce = DraggingConfig.AlignPositionMaxForce alignPos.Responsiveness = DraggingConfig.AlignPositionResponsiveness alignPos.Parent = targetPart local alignOri = Instance.new("AlignOrientation") alignOri.Attachment0 = partAttachment alignOri.Attachment1 = targetAttachment alignOri.MaxTorque = DraggingConfig.AlignOrientationMaxTorque alignOri.Responsiveness = DraggingConfig.AlignOrientationResponsiveness alignOri.Parent = targetPart -- Grant Network Ownership to the Player to eliminate latency rubber-banding pcall(function() targetPart:SetNetworkOwner(player) end) -- Save the state activeDrags[player.UserId] = { part = targetPart, dragAttachment = partAttachment, targetAttachment = targetAttachment, alignPos = alignPos, alignOri = alignOri } end -- Handle Drop Event Request local function onDropRequest(player) dropObject(player) end -- Players leaving cleanup Players.PlayerRemoving:Connect(function(player) dropObject(player) end) DragEvent.OnServerEvent:Connect(onDragRequest) DropEvent.OnServerEvent:Connect(onDropRequest)