-- src/StarterPlayer/StarterCharacterScripts/DragController.client.lua local Players = game:GetService("Players") local UserInputService = game:GetService("UserInputService") local ReplicatedStorage = game:GetService("ReplicatedStorage") local RunService = game:GetService("RunService") local CollectionService = game:GetService("CollectionService") local player = Players.LocalPlayer local mouse = player:GetMouse() local camera = workspace.CurrentCamera local character = player.Character or player.CharacterAdded:Wait() local DraggingConfig = require(ReplicatedStorage:WaitForChild("Shared"):WaitForChild("DraggingConfig")) local DragEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("DragEvent") local DropEvent = ReplicatedStorage:WaitForChild("Events"):WaitForChild("DropEvent") local isDragging = false local draggedPart = nil local targetAttachment = nil local currentHoldDistance = DraggingConfig.HoldDistance local highlightedPart = nil local highlight = nil local renderConnection = nil local hoverConnection = nil -- Create highlight effect local function createHighlight(part) removeHighlight() if not part then return end highlight = Instance.new("SelectionBox") highlight.Adornee = part highlight.Color3 = Color3.fromRGB(100, 200, 255) highlight.LineThickness = 0.03 highlight.SurfaceTransparency = 0.8 highlight.SurfaceColor3 = Color3.fromRGB(100, 200, 255) highlight.Parent = part highlightedPart = part end function removeHighlight() if highlight then highlight:Destroy() highlight = nil end highlightedPart = nil end -- Hover detection for draggable parts local function updateHover() if isDragging then return end local target = mouse.Target if target and (CollectionService:HasTag(target, "Draggable") or CollectionService:HasTag(target, "TreeSegment")) then if target ~= highlightedPart then createHighlight(target) end else removeHighlight() end end local function updateDraggingPosition() if not isDragging or not draggedPart or not targetAttachment then return end local char = player.Character if not char then return end local head = char:FindFirstChild("Head") if not head then return end local rayOrigin = head.Position local rayDirection = camera.CFrame.LookVector * currentHoldDistance local raycastParams = RaycastParams.new() raycastParams.FilterDescendantsInstances = {char, draggedPart} raycastParams.FilterType = Enum.RaycastFilterType.Exclude local raycastResult = workspace:Raycast(rayOrigin, rayDirection, raycastParams) local targetPos = rayOrigin + rayDirection if raycastResult then targetPos = raycastResult.Position end targetAttachment.WorldCFrame = CFrame.new(targetPos) * CFrame.Angles(0, camera.CFrame.Rotation.Y, 0) end local function stopDragging() if not isDragging then return end isDragging = false draggedPart = nil targetAttachment = nil currentHoldDistance = DraggingConfig.HoldDistance if renderConnection then renderConnection:Disconnect() renderConnection = nil end DropEvent:FireServer() end local function onInputBegan(input, gameProcessed) if gameProcessed then return end if input.UserInputType == Enum.UserInputType.MouseButton1 then if isDragging then return end local targetPart = mouse.Target if not targetPart then return end if not CollectionService:HasTag(targetPart, "Draggable") and not CollectionService:HasTag(targetPart, "TreeSegment") then return end local char = player.Character if not char or not char:FindFirstChild("HumanoidRootPart") then return end local distance = (char.HumanoidRootPart.Position - targetPart.Position).Magnitude if distance > DraggingConfig.MaxGrabDistance then return end removeHighlight() DragEvent:FireServer(targetPart, mouse.Hit.Position) local attachmentName = "TargetAttachment_Player" .. player.UserId local t = 0 while not workspace.Terrain:FindFirstChild(attachmentName) and t < 1 do t = t + task.wait() end targetAttachment = workspace.Terrain:FindFirstChild(attachmentName) if targetAttachment then isDragging = true draggedPart = targetPart currentHoldDistance = DraggingConfig.HoldDistance renderConnection = RunService.RenderStepped:Connect(updateDraggingPosition) end end end local function onInputEnded(input, gameProcessed) if input.UserInputType == Enum.UserInputType.MouseButton1 then stopDragging() end end -- Scroll wheel for hold distance adjustment local function onInputChanged(input) if not isDragging then return end if input.UserInputType == Enum.UserInputType.MouseWheel then currentHoldDistance = math.clamp( currentHoldDistance + input.Position.Z * 2, 3, DraggingConfig.MaxGrabDistance ) end end -- Respawn handling player.CharacterAdded:Connect(function(char) character = char camera = workspace.CurrentCamera stopDragging() end) -- Hover detection loop hoverConnection = RunService.RenderStepped:Connect(updateHover) UserInputService.InputBegan:Connect(onInputBegan) UserInputService.InputEnded:Connect(onInputEnded) UserInputService.InputChanged:Connect(onInputChanged)