local T=Core.RbxUtility.CreateSignal()function StartSnapping()AttachHandles(nil,true)BoundingBox.ClearBoundingBox()if Dragging then SnapTracking.TargetBlacklist=Selection.Items end SnapTracking.StartTracking(function(E)if E then SnappedPoint=E.p T:fire(SnappedPoint)end end)end function SetAxisPosition(T,E)TrackChange()local i=PreparePartsForDragging()for i in pairs(i)do i.CFrame=CFrame.new(T=="\088"and E or i.Position.X,T=="\089"and E or i.Position.Y,T=="\090"and E or i.Position.Z)*(i.CFrame-i.CFrame.p)end local C=Security.GetPermissions(Security.GetSelectionAreas(Selection.Items),Core.Player)if Core.Mode=="\084\111\111\108"and Security.ArePartsViolatingAreas(Selection.Items,Core.Player,false,C)then for T,E in pairs(i)do T.CFrame=E.CFrame end end for T,E in pairs(i)do T:MakeJoints()Core.RestoreJoints(E.Joints)T.CanCollide=E.CanCollide T.Anchored=E.Anchored end RegisterChange()end function NudgeSelectionByFace(T)local E=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 E=-E end TrackChange()local C=PreparePartsForDragging()MovePartsAlongAxesByFace(T,E,MoveTool.Axes,Selection.Focus,C)if MoveTool.UI then MoveTool.UI.Changes.Text.Text="\109\111\118\101\100 "..(math.abs(E).." \115\116\117\100\115")end local W=Security.GetPermissions(Security.GetSelectionAreas(Selection.Items),Core.Player)if Core.Mode=="\084\111\111\108"and Security.ArePartsViolatingAreas(Selection.Items,Core.Player,false,W)then for T,E in pairs(C)do T.CFrame=E.CFrame end end for T,E in pairs(C)do T:MakeJoints()Core.RestoreJoints(E.Joints)T.CanCollide=E.CanCollide T.Anchored=E.Anchored end RegisterChange()end function TrackChange()HistoryRecord={Parts=Support.CloneTable(Selection.Items),BeforeCFrame={},AfterCFrame={};Unapply=function(T)Selection.Replace(T.Parts)local E={}for i,C in pairs(T.Parts)do table.insert(E,{Part=C,CFrame=T.BeforeCFrame[C]})end Core.SyncAPI:Invoke("\083\121\110\099\077\111\118\101",E)end;Apply=function(T)Selection.Replace(T.Parts)local E={}for i,C in pairs(T.Parts)do table.insert(E,{Part=C;CFrame=T.AfterCFrame[C]})end Core.SyncAPI:Invoke("\083\121\110\099\077\111\118\101",E)end}for T,E in pairs(HistoryRecord.Parts)do HistoryRecord.BeforeCFrame[E]=E.CFrame end end function RegisterChange()if not HistoryRecord then return end local T={}for E,i in pairs(HistoryRecord.Parts)do HistoryRecord.AfterCFrame[i]=i.CFrame table.insert(T,{Part=i,CFrame=i.CFrame})end Core.SyncAPI:Invoke("\083\121\110\099\077\111\118\101",T)Core.History.Add(HistoryRecord)HistoryRecord=nil end function EnableDragging()Connections.DragStart=Core.Mouse.Button1Down:connect(function()local T=Core.Mouse.Target if Selection.Multiselecting then return end local E=UserInputService:IsKeyDown(Enum.KeyCode.R)and#Selection.Items>0 if not Core.IsSelectable(T)and not E then return end DragStartTarget=E and Selection.Focus or T DragStart=Vector2.new(Core.Mouse.X,Core.Mouse.Y)if not Selection.IsSelected(T)and not E then Selection.Replace({T},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

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;
