local x=Core.RbxUtility.CreateSignal()function StartSnapping()AttachHandles(nil,true)BoundingBox.ClearBoundingBox()if Dragging then SnapTracking.TargetBlacklist=Selection.Items end SnapTracking.StartTracking(function(k)if k then SnappedPoint=k.p x:fire(SnappedPoint)end end)end function SetAxisPosition(x,k)TrackChange()local i=PreparePartsForDragging()for i in pairs(i)do i.CFrame=CFrame.new(x=="\088"and k or i.Position.X,x=="\089"and k or i.Position.Y,x=="\090"and k or i.Position.Z)*(i.CFrame-i.CFrame.p)end local L=Security.GetPermissions(Security.GetSelectionAreas(Selection.Items),Core.Player)if Core.Mode=="\084\111\111\108"and Security.ArePartsViolatingAreas(Selection.Items,Core.Player,false,L)then for x,k in pairs(i)do x.CFrame=k.CFrame end end for x,k in pairs(i)do x:MakeJoints()Core.RestoreJoints(k.Joints)x.CanCollide=k.CanCollide x.Anchored=k.Anchored end RegisterChange()end function NudgeSelectionByFace(x)local k=MoveTool.Increment local i=Support.FlipTable(Support.GetListMembers(UserInputService:GetKeysPressed(),"\075\101\121\067\111\100\101"))if i[Enum.KeyCode.LeftShift]or i[Enum.KeyCode.RightShift]then k=-k end TrackChange()local L=PreparePartsForDragging()MovePartsAlongAxesByFace(x,k,MoveTool.Axes,Selection.Focus,L)if MoveTool.UI then MoveTool.UI.Changes.Text.Text="\109\111\118\101\100 "..(math.abs(k).." \115\116\117\100\115")end local E=Security.GetPermissions(Security.GetSelectionAreas(Selection.Items),Core.Player)if Core.Mode=="\084\111\111\108"and Security.ArePartsViolatingAreas(Selection.Items,Core.Player,false,E)then for x,k in pairs(L)do x.CFrame=k.CFrame end end for x,k in pairs(L)do x:MakeJoints()Core.RestoreJoints(k.Joints)x.CanCollide=k.CanCollide x.Anchored=k.Anchored end RegisterChange()end function TrackChange()HistoryRecord={Parts=Support.CloneTable(Selection.Items),BeforeCFrame={};AfterCFrame={},Unapply=function(x)Selection.Replace(x.Parts)local k={}for i,L in pairs(x.Parts)do table.insert(k,{Part=L;CFrame=x.BeforeCFrame[L]})end Core.SyncAPI:Invoke("\083\121\110\099\077\111\118\101",k)end;Apply=function(x)Selection.Replace(x.Parts)local k={}for i,L in pairs(x.Parts)do table.insert(k,{Part=L;CFrame=x.AfterCFrame[L]})end Core.SyncAPI:Invoke("\083\121\110\099\077\111\118\101",k)end}for x,k in pairs(HistoryRecord.Parts)do HistoryRecord.BeforeCFrame[k]=k.CFrame end end function RegisterChange()if not HistoryRecord then return end local x={}for k,i in pairs(HistoryRecord.Parts)do HistoryRecord.AfterCFrame[i]=i.CFrame table.insert(x,{Part=i,CFrame=i.CFrame})end Core.SyncAPI:Invoke("\083\121\110\099\077\111\118\101",x)Core.History.Add(HistoryRecord)HistoryRecord=nil end function EnableDragging()Connections.DragStart=Core.Mouse.Button1Down:connect(function()local x=Core.Mouse.Target if Selection.Multiselecting then return end local k=UserInputService:IsKeyDown(Enum.KeyCode.R)and#Selection.Items>0 if not Core.IsSelectable(x)and not k then return end DragStartTarget=k and Selection.Focus or x DragStart=Vector2.new(Core.Mouse.X,Core.Mouse.Y)if not Selection.IsSelected(x)and not k then Selection.Replace({x},true)end Connections.WatchForDrag=Core.Mouse.Move:connect(function()if DragStart and(Vector2.new(Core.Mouse.X,Core.Mouse.Y)-DragStart).magnitude>=2 then BoundingBox.ClearBoundingBox()SetUpDragging(DragStartTarget,SnapTracking.Enabled and SnappedPoint or nil)ClearConnection("\087\097\116\099\104\070\111\114\068\114\097\103")end end)end)end
-- Event that fires when new point comes into focus while snapping
local PointSnapped = Core.RbxUtility.CreateSignal()

function StartSnapping()
	-- Starts tracking snap points nearest to the mouse

	-- Hide any handles or bounding boxes
	AttachHandles(nil, true)
	BoundingBox.ClearBoundingBox()

	-- Avoid targeting snap points in selected parts while dragging
	if Dragging then
		SnapTracking.TargetBlacklist = Selection.Items
	end

	-- Start tracking the closest snapping point
	SnapTracking.StartTracking(function(NewPoint)
		-- Fire `SnappedPoint` and update `SnappedPoint` when there is a new snap point in focus
		if NewPoint then
			SnappedPoint = NewPoint.p
			PointSnapped:fire(SnappedPoint)
		end
	end)
end

function SetAxisPosition(Axis, Position)
	-- Sets the selection's position on axis `Axis` to `Position`

	-- Track this change
	TrackChange()

	-- Prepare parts to be moved
	local InitialStates = PreparePartsForDragging()

	-- Update each part
	for Part in pairs(InitialStates) do
		-- Set the part's new CFrame
		Part.CFrame = CFrame.new(
			Axis == "X" and Position or Part.Position.X,
			Axis == "Y" and Position or Part.Position.Y,
			Axis == "Z" and Position or Part.Position.Z
		) * (Part.CFrame - Part.CFrame.p)
	end

	-- Cache up permissions for all private areas
	local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Selection.Items), Core.Player)

	-- Revert changes if player is not authorized to move parts to target destination
	if
		Core.Mode == "Tool" and Security.ArePartsViolatingAreas(Selection.Items, Core.Player, false, AreaPermissions)
	then
		for Part, State in pairs(InitialStates) do
			Part.CFrame = State.CFrame
		end
	end

	-- Restore the parts' original states
	for Part, State in pairs(InitialStates) do
		Part:MakeJoints()
		Core.RestoreJoints(State.Joints)
		Part.CanCollide = State.CanCollide
		Part.Anchored = State.Anchored
	end

	-- Register the change
	RegisterChange()
end

function NudgeSelectionByFace(Face)
	-- Nudges the selection along the current axes mode in the direction of the focused part's face

	-- Get amount to nudge by
	local NudgeAmount = MoveTool.Increment

	-- Reverse nudge amount if shift key is held while nudging
	local PressedKeys = Support.FlipTable(Support.GetListMembers(UserInputService:GetKeysPressed(), "KeyCode"))
	if PressedKeys[Enum.KeyCode.LeftShift] or PressedKeys[Enum.KeyCode.RightShift] then
		NudgeAmount = -NudgeAmount
	end

	-- Track this change
	TrackChange()

	-- Prepare parts to be moved
	local InitialState = PreparePartsForDragging()

	-- Perform the movement
	MovePartsAlongAxesByFace(Face, NudgeAmount, MoveTool.Axes, Selection.Focus, InitialState)

	-- Update the "distance moved" indicator
	if MoveTool.UI then
		MoveTool.UI.Changes.Text.Text = "moved " .. math.abs(NudgeAmount) .. " studs"
	end

	-- Cache up permissions for all private areas
	local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Selection.Items), Core.Player)

	-- Revert changes if player is not authorized to move parts to target destination
	if
		Core.Mode == "Tool" and Security.ArePartsViolatingAreas(Selection.Items, Core.Player, false, AreaPermissions)
	then
		for Part, State in pairs(InitialState) do
			Part.CFrame = State.CFrame
		end
	end

	-- Restore the parts' original states
	for Part, State in pairs(InitialState) do
		Part:MakeJoints()
		Core.RestoreJoints(State.Joints)
		Part.CanCollide = State.CanCollide
		Part.Anchored = State.Anchored
	end

	-- Register the change
	RegisterChange()
end

function TrackChange()
	-- Start the record
	HistoryRecord = {
		Parts = Support.CloneTable(Selection.Items),
		BeforeCFrame = {},
		AfterCFrame = {},

		Unapply = function(Record)
			-- Reverts this change

			-- Select the changed parts
			Selection.Replace(Record.Parts)

			-- Put together the change request
			local Changes = {}
			for _, Part in pairs(Record.Parts) do
				table.insert(Changes, { Part = Part, CFrame = Record.BeforeCFrame[Part] })
			end

			-- Send the change request
			Core.SyncAPI:Invoke("SyncMove", Changes)
		end,

		Apply = function(Record)
			-- Applies this change

			-- Select the changed parts
			Selection.Replace(Record.Parts)

			-- Put together the change request
			local Changes = {}
			for _, Part in pairs(Record.Parts) do
				table.insert(Changes, { Part = Part, CFrame = Record.AfterCFrame[Part] })
			end

			-- Send the change request
			Core.SyncAPI:Invoke("SyncMove", Changes)
		end,
	}

	-- Collect the selection's initial state
	for _, Part in pairs(HistoryRecord.Parts) do
		HistoryRecord.BeforeCFrame[Part] = Part.CFrame
	end
end

function RegisterChange()
	-- Finishes creating the history record and registers it

	-- Make sure there's an in-progress history record
	if not HistoryRecord then
		return
	end

	-- Collect the selection's final state
	local Changes = {}
	for _, Part in pairs(HistoryRecord.Parts) do
		HistoryRecord.AfterCFrame[Part] = Part.CFrame
		table.insert(Changes, { Part = Part, CFrame = Part.CFrame })
	end

	-- Send the change to the server
	Core.SyncAPI:Invoke("SyncMove", Changes)

	-- Register the record and clear the staging
	Core.History.Add(HistoryRecord)
	HistoryRecord = nil
end

function EnableDragging()
	-- Enables part dragging

	-- Pay attention to when the user intends to start dragging
	Connections.DragStart = Core.Mouse.Button1Down:connect(function()
		-- Get mouse target
		local TargetPart = Core.Mouse.Target

		-- Make sure this click was not to select
		if Selection.Multiselecting then
			return
		end

		-- Check whether the user is snapping
		local IsSnapping = UserInputService:IsKeyDown(Enum.KeyCode.R) and #Selection.Items > 0

		-- Make sure target is draggable, unless snapping is ongoing
		if not Core.IsSelectable(TargetPart) and not IsSnapping then
			return
		end

		-- Initialize dragging detection data
		DragStartTarget = IsSnapping and Selection.Focus or TargetPart
		DragStart = Vector2.new(Core.Mouse.X, Core.Mouse.Y)

		-- Select the target if it's not selected, and snapping is not ongoing
		if not Selection.IsSelected(TargetPart) and not IsSnapping then
			Selection.Replace({ TargetPart }, true)
		end

		-- Watch for potential dragging
		Connections.WatchForDrag = Core.Mouse.Move:connect(function()
			-- Trigger dragging if the mouse is moved over 2 pixels
			if DragStart and (Vector2.new(Core.Mouse.X, Core.Mouse.Y) - DragStart).magnitude >= 2 then
				-- Prepare for dragging
				BoundingBox.ClearBoundingBox()
				SetUpDragging(DragStartTarget, SnapTracking.Enabled and SnappedPoint or nil)

				-- Disable watching for potential dragging
				ClearConnection("WatchForDrag")
			end
		end)
	end)
end
