Support.AddUserInputListener("\069\110\100\101\100","\077\111\117\115\101\066\117\116\116\111\110\049",true,function(g)if not HandleRotating then return end Core.Targeting.CancelSelecting()HandleRotating=false ClearConnection("\072\097\110\100\108\101\082\101\108\101\097\115\101")HandleDirection=nil HandleFirstAngle=nil LastDisplayedRotation=nil for g,V in pairs(InitialState)do g:MakeJoints()Core.RestoreJoints(V.Joints)g.CanCollide=V.CanCollide g.Anchored=V.Anchored end RegisterChange()BoundingBox.RecalculateStaticExtents()BoundingBox.ResumeMonitoring()end)function HideHandles()if not Handles or not Handles.Visible then return end Handles.Visible=false Handles.Parent=nil ClearConnection("\065\117\116\111\102\111\099\117\115\072\097\110\100\108\101")end function RotatePartsAroundPivot(g,V,S,I,J)local R=CFrame.fromAxisAngle(Vector3.FromAxis(S),math.rad(I))for S,I in pairs(J)do if g=="\067\101\110\116\101\114"or g=="\076\097\115\116"then local g=V*R local J=V:toObjectSpace(I.CFrame)S.CFrame=g*J elseif RotateTool.Pivot=="\076\111\099\097\108"then S.CFrame=I.CFrame*R end end end function GetHandleDisplayDelta(g)if HandleFirstAngle==nil then HandleFirstAngle=true HandleDirection=true elseif HandleFirstAngle==true then if math.abs(g)>180 then HandleDirection=false else HandleDirection=true end HandleFirstAngle=false end local V if HandleDirection==true then V=(360-g)%360 else V=g%360 end if LastDisplayedRotation and(LastDisplayedRotation<=120 and V>=240 or LastDisplayedRotation>=240 and V<=120)then HandleDirection=not HandleDirection end if HandleDirection==true then V=(360-g)%360 else V=g%360 end LastDisplayedRotation=V return V end function BindShortcutKeys()table.insert(Connections,UserInputService.InputBegan:connect(function(g,V)if V then return end if g.UserInputType~=Enum.UserInputType.Keyboard then return end if UserInputService:GetFocusedTextBox()then return end if g.KeyCode==Enum.KeyCode.Return or g.KeyCode==Enum.KeyCode.KeypadEnter then if RotateTool.Pivot=="\067\101\110\116\101\114"then SetPivot("\076\111\099\097\108")elseif RotateTool.Pivot=="\076\111\099\097\108"then SetPivot("\076\097\115\116")elseif RotateTool.Pivot=="\076\097\115\116"then SetPivot("\067\101\110\116\101\114")end elseif g.KeyCode==Enum.KeyCode.Minus or g.KeyCode==Enum.KeyCode.KeypadMinus then if RotateTool.UI then RotateTool.UI.IncrementOption.Increment.TextBox:CaptureFocus()end elseif g.KeyCode==Enum.KeyCode.KeypadEight then NudgeSelectionByAxis(Enum.Axis.X,1)elseif g.KeyCode==Enum.KeyCode.KeypadTwo then NudgeSelectionByAxis(Enum.Axis.X,-1)elseif g.KeyCode==Enum.KeyCode.KeypadNine then NudgeSelectionByAxis(Enum.Axis.Z,1)elseif g.KeyCode==Enum.KeyCode.KeypadOne then NudgeSelectionByAxis(Enum.Axis.Z,-1)elseif g.KeyCode==Enum.KeyCode.KeypadFour then NudgeSelectionByAxis(Enum.Axis.Y,-1)elseif g.KeyCode==Enum.KeyCode.KeypadSix then NudgeSelectionByAxis(Enum.Axis.Y,1)elseif g.KeyCode==Enum.KeyCode.R and not Selection.Multiselecting then StartSnapping()elseif g.KeyCode==Enum.KeyCode.T then StartSnapping()end end))end function StartSnapping()if SnapTracking.Enabled then return end SnapTracking.StartTracking(function(g)SnappedPoint=g end)Connections.SelectSnappedPivot=Core.Mouse.Button1Down:connect(function()Core.Targeting.CancelSelecting()if not SnappedPoint then return end SnapTracking.StopTracking()local g=(Create("\080\097\114\116"))({CFrame=SnappedPoint;Size=Vector3.new(5,1,5)})SetPivot("\076\097\115\116")AttachHandles(g,true)GCBypass={g}PivotPoint=SnappedPoint CustomPivotPoint=true ClearConnection("\083\101\108\101\099\116\083\110\097\112\112\101\100\080\105\118\111\116")DisableCustomPivotPoint=Handles.Changed:Connect(function(g)if g=="\065\100\111\114\110\101\101"then CustomPivotPoint=false DisableCustomPivotPoint:Disconnect()end end)end)end function SetAxisAngle(g,V)local S=math.rad(V)TrackChange()local I=PreparePartsForRotating()for V,I in pairs(I)do V.CFrame=CFrame.new(V.Position)*CFrame.fromOrientation(g=="\088"and S or math.rad(V.Orientation.X),g=="\089"and S or math.rad(V.Orientation.Y),g=="\090"and S or math.rad(V.Orientation.Z))end local J=Security.GetPermissions(Security.GetSelectionAreas(Selection.Items),Core.Player)if Core.Mode=="\084\111\111\108"and Security.ArePartsViolatingAreas(Selection.Items,Core.Player,false,J)then for g,V in pairs(I)do g.CFrame=V.CFrame end end for g,V in pairs(I)do g:MakeJoints()Core.RestoreJoints(V.Joints)g.CanCollide=V.CanCollide g.Anchored=V.Anchored end RegisterChange()end function NudgeSelectionByAxis(g,V)if#Selection.Items==0 then return end local S=RotateTool.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 S=-S end TrackChange()local J=PreparePartsForRotating()if RotateTool.Pivot=="\067\101\110\116\101\114"then local g,V=BoundingBox.CalculateExtents(Selection.Items)PivotPoint=V elseif RotateTool.Pivot=="\076\097\115\116"and not CustomPivotPoint then PivotPoint=J[Selection.Focus].CFrame end RotatePartsAroundPivot(RotateTool.Pivot,PivotPoint,g,S*(V or 1),J)if RotateTool.UI then RotateTool.UI.Changes.Text.Text="\114\111\116\097\116\101\100 "..(S*(V or 1).." \100\101\103\114\101\101\115")end local R=Security.GetPermissions(Security.GetSelectionAreas(Selection.Items),Core.Player)if Core.Mode=="\084\111\111\108"and Security.ArePartsViolatingAreas(Selection.Items,Core.Player,false,R)then for g,V in pairs(J)do g.CFrame=V.CFrame end end for g,V in pairs(J)do g:MakeJoints()Core.RestoreJoints(V.Joints)g.CanCollide=V.CanCollide g.Anchored=V.Anchored end RegisterChange()end function TrackChange()HistoryRecord={Parts=Support.CloneTable(Selection.Items),BeforeCFrame={},AfterCFrame={};Unapply=function(g)Selection.Replace(g.Parts)local V={}for S,I in pairs(g.Parts)do table.insert(V,{Part=I,CFrame=g.BeforeCFrame[I]})end Core.SyncAPI:Invoke("\083\121\110\099\082\111\116\097\116\101",V)end;Apply=function(g)Selection.Replace(g.Parts)local V={}for S,I in pairs(g.Parts)do table.insert(V,{Part=I;CFrame=g.AfterCFrame[I]})end Core.SyncAPI:Invoke("\083\121\110\099\082\111\116\097\116\101",V)end}for g,V in pairs(HistoryRecord.Parts)do HistoryRecord.BeforeCFrame[V]=V.CFrame end end function RegisterChange()if not HistoryRecord then return end local g={}for V,S in pairs(HistoryRecord.Parts)do HistoryRecord.AfterCFrame[S]=S.CFrame table.insert(g,{Part=S;CFrame=S.CFrame})end Core.SyncAPI:Invoke("\083\121\110\099\082\111\116\097\116\101",g)Core.History.Add(HistoryRecord)HistoryRecord=nil end function PreparePartsForRotating()local g={}local V=Support.FlipTable(Selection.Items)for S,I in pairs(Selection.Items)do g[I]={Anchored=I.Anchored;CanCollide=I.CanCollide;CFrame=I.CFrame}I.Anchored=true I.CanCollide=false g[I].Joints=Core.PreserveJoints(I,V)I:BreakJoints()I.Velocity=Vector3.new()I.RotVelocity=Vector3.new()end return g end function GetIncrementMultiple(g,V)local S=g%V local I=g-S local J=(g-S)+V local R=math.abs(g-I)local s=math.abs(g-J)if R<=s then g=I else g=J end return g end

Support.AddUserInputListener('Ended', 'MouseButton1', true, function (Input)

	-- Make sure rotating is ongoing
	if not HandleRotating then
		return;
	end;

	-- Prevent selection
	Core.Targeting.CancelSelecting();

	-- Disable rotating
	HandleRotating = false;

	-- Clear this connection to prevent it from firing again
	ClearConnection 'HandleRelease';

	-- Clear change indicator states
	HandleDirection = nil;
	HandleFirstAngle = nil;
	LastDisplayedRotation = nil;

	-- Make joints, restore original anchor and collision 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();

	-- Resume normal bounding box updating
	BoundingBox.RecalculateStaticExtents();
	BoundingBox.ResumeMonitoring();

end);

function HideHandles()
	-- Hides the resizing handles

	-- Make sure handles exist and are visible
	if not Handles or not Handles.Visible then
		return;
	end;

	-- Hide the handles
	Handles.Visible = false;
	Handles.Parent = nil;

	-- Disable handle autofocus if enabled
	ClearConnection 'AutofocusHandle';

end;

function RotatePartsAroundPivot(PivotMode, PivotPoint, Axis, Rotation, InitialStates)
	-- Rotates the given parts in `InitialStates` around `PivotMode` (using `PivotPoint` if applicable)'s `Axis` by `Rotation`

	-- Create a CFrame that increments rotation by `Rotation` around `Axis`
	local RotationCFrame = CFrame.fromAxisAngle(Vector3.FromAxis(Axis), math.rad(Rotation));

	-- Rotate each part
	for Part, InitialState in pairs(InitialStates) do

		-- Rotate around the selection's center, or the currently focused part
		if PivotMode == 'Center' or PivotMode == 'Last' then

			-- Calculate the focused part's rotation
			local RelativeTo = PivotPoint * RotationCFrame;

			-- Calculate this part's offset from the focused part's rotation
			local Offset = PivotPoint:toObjectSpace(InitialState.CFrame);

			-- Rotate relative to the focused part by this part's offset from it
			Part.CFrame = RelativeTo * Offset;

		-- Rotate around the part's center
		elseif RotateTool.Pivot == 'Local' then
			Part.CFrame = InitialState.CFrame * RotationCFrame;

		end;

	end;

end;

function GetHandleDisplayDelta(HandleRotation)
	-- Returns a human-friendly version of the handle's rotation delta

	-- Prepare to capture first angle
	if HandleFirstAngle == nil then
		HandleFirstAngle = true;
		HandleDirection = true;

	-- Capture first angle
	elseif HandleFirstAngle == true then

		-- Determine direction based on first angle
		if math.abs(HandleRotation) > 180 then
			HandleDirection = false;
		else
			HandleDirection = true;
		end;

		-- Disable first angle capturing
		HandleFirstAngle = false;

	end;

	-- Determine the rotation delta to display
	local DisplayedRotation;
	if HandleDirection == true then
		DisplayedRotation = (360 - HandleRotation) % 360;
	else
		DisplayedRotation = HandleRotation % 360;
	end;

	-- Switch delta calculation direction if crossing directions
	if LastDisplayedRotation and (
	   (LastDisplayedRotation <= 120 and DisplayedRotation >= 240) or
	   (LastDisplayedRotation >= 240 and DisplayedRotation <= 120)) then
		HandleDirection = not HandleDirection;
	end;

	-- Update displayed rotation after direction correction
	if HandleDirection == true then
		DisplayedRotation = (360 - HandleRotation) % 360;
	else
		DisplayedRotation = HandleRotation % 360;
	end;

	-- Store this last display rotation
	LastDisplayedRotation = DisplayedRotation;

	-- Return updated display delta
	return DisplayedRotation;

end;

function BindShortcutKeys()
	-- Enables useful shortcut keys for this tool

	-- Track user input while this tool is equipped
	table.insert(Connections, UserInputService.InputBegan:connect(function (InputInfo, GameProcessedEvent)

		-- Make sure this is an intentional event
		if GameProcessedEvent then
			return;
		end;

		-- Make sure this input is a key press
		if InputInfo.UserInputType ~= Enum.UserInputType.Keyboard then
			return;
		end;

		-- Make sure it wasn't pressed while typing
		if UserInputService:GetFocusedTextBox() then
			return;
		end;

		-- Check if the enter key was pressed
		if InputInfo.KeyCode == Enum.KeyCode.Return or InputInfo.KeyCode == Enum.KeyCode.KeypadEnter then

			-- Toggle the current axis mode
			if RotateTool.Pivot == 'Center' then
				SetPivot('Local');

			elseif RotateTool.Pivot == 'Local' then
				SetPivot('Last');

			elseif RotateTool.Pivot == 'Last' then
				SetPivot('Center');
			end;

		-- Check if the - key was pressed
		elseif InputInfo.KeyCode == Enum.KeyCode.Minus or InputInfo.KeyCode == Enum.KeyCode.KeypadMinus then

			-- Focus on the increment input
			if RotateTool.UI then
				RotateTool.UI.IncrementOption.Increment.TextBox:CaptureFocus();
			end;

		-- Nudge around X axis if the 8 button on the keypad is pressed
		elseif InputInfo.KeyCode == Enum.KeyCode.KeypadEight then
			NudgeSelectionByAxis(Enum.Axis.X, 1);

		-- Nudge around X axis if the 2 button on the keypad is pressed
		elseif InputInfo.KeyCode == Enum.KeyCode.KeypadTwo then
			NudgeSelectionByAxis(Enum.Axis.X, -1);

		-- Nudge around Z axis if the 9 button on the keypad is pressed
		elseif InputInfo.KeyCode == Enum.KeyCode.KeypadNine then
			NudgeSelectionByAxis(Enum.Axis.Z, 1);

		-- Nudge around Z axis if the 1 button on the keypad is pressed
		elseif InputInfo.KeyCode == Enum.KeyCode.KeypadOne then
			NudgeSelectionByAxis(Enum.Axis.Z, -1);

		-- Nudge around Y axis if the 4 button on the keypad is pressed
		elseif InputInfo.KeyCode == Enum.KeyCode.KeypadFour then
			NudgeSelectionByAxis(Enum.Axis.Y, -1);

		-- Nudge around Y axis if the 6 button on the keypad is pressed
		elseif InputInfo.KeyCode == Enum.KeyCode.KeypadSix then
			NudgeSelectionByAxis(Enum.Axis.Y, 1);

		-- Start snapping when the R key is pressed down, and it's not the selection clearing hotkey
		elseif (InputInfo.KeyCode == Enum.KeyCode.R) and not Selection.Multiselecting then
			StartSnapping();

		-- Start snapping when T key is pressed down (alias)
		elseif InputInfo.KeyCode == Enum.KeyCode.T then
			StartSnapping();

		end;

	end));

end;

function StartSnapping()

	-- Make sure snapping isn't already enabled
	if SnapTracking.Enabled then
		return;
	end;

	-- Listen for snapped points
	SnapTracking.StartTracking(function (NewPoint)
		SnappedPoint = NewPoint;
	end);

	-- Select the snapped pivot point upon clicking
	Connections.SelectSnappedPivot = Core.Mouse.Button1Down:connect(function ()

		-- Disable unintentional selection
		Core.Targeting.CancelSelecting();

		-- Ensure there is a snap point
		if not SnappedPoint then
			return;
		end;

		-- Disable snapping
		SnapTracking.StopTracking();

		-- Attach the handles to a part at the snapped point
		local Part = Create 'Part' {
			CFrame = SnappedPoint,
			Size = Vector3.new(5, 1, 5)
		};
		SetPivot 'Last';
		AttachHandles(Part, true);

		-- Maintain the part in memory to prevent garbage collection
		GCBypass = { Part };

		-- Set the pivot point
		PivotPoint = SnappedPoint;
		CustomPivotPoint = true;

		-- Disconnect snapped pivot point selection listener
		ClearConnection 'SelectSnappedPivot';

		-- Disable custom pivot point mode when the handles attach elsewhere
		DisableCustomPivotPoint = Handles.Changed:Connect(function (Property)
			if Property == 'Adornee' then
				CustomPivotPoint = false;
				DisableCustomPivotPoint:Disconnect();
			end;
		end);

	end);

end;

function SetAxisAngle(Axis, Angle)
	-- Sets the selection's angle on axis `Axis` to `Angle`

	-- Turn the given angle from degrees to radians
	local Angle = math.rad(Angle);

	-- Track this change
	TrackChange();

	-- Prepare parts to be moved
	local InitialStates = PreparePartsForRotating();

	-- Update each part
	for Part, State in pairs(InitialStates) do

		-- Set the part's new CFrame
		Part.CFrame = CFrame.new(Part.Position) * CFrame.fromOrientation(
			Axis == 'X' and Angle or math.rad(Part.Orientation.X),
			Axis == 'Y' and Angle or math.rad(Part.Orientation.Y),
			Axis == 'Z' and Angle or math.rad(Part.Orientation.Z)
		);

	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 NudgeSelectionByAxis(Axis, Direction)
	-- Nudges the rotation of the selection in the direction of the given axis

	-- Ensure selection is not empty
	if #Selection.Items == 0 then
		return;
	end;

	-- Get amount to nudge by
	local NudgeAmount = RotateTool.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 the change
	TrackChange();

	-- Stop parts from moving, and capture the initial state of the parts
	local InitialState = PreparePartsForRotating();

	-- Set the pivot point to the center of the selection if in Center mode
	if RotateTool.Pivot == 'Center' then
		local BoundingBoxSize, BoundingBoxCFrame = BoundingBox.CalculateExtents(Selection.Items);
		PivotPoint = BoundingBoxCFrame;

	-- Set the pivot point to the center of the focused part if in Last mode
	elseif RotateTool.Pivot == 'Last' and not CustomPivotPoint then
		PivotPoint = InitialState[Selection.Focus].CFrame;
	end;

	-- Perform the rotation
	RotatePartsAroundPivot(RotateTool.Pivot, PivotPoint, Axis, NudgeAmount * (Direction or 1), InitialState);

	-- Update the "degrees rotated" indicator
	if RotateTool.UI then
		RotateTool.UI.Changes.Text.Text = 'rotated ' .. (NudgeAmount * (Direction or 1)) .. ' degrees';
	end;

	-- Cache area permissions information
	local AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Selection.Items), Core.Player);

	-- Make sure we're not entering any unauthorized private areas
	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;

	-- Make joints, restore original anchor and collision 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('SyncRotate', 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('SyncRotate', 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('SyncRotate', Changes);

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

end;

function PreparePartsForRotating()
	-- Prepares parts for rotating and returns the initial state of the parts

	local InitialState = {};

	-- Get index of parts
	local PartIndex = Support.FlipTable(Selection.Items);

	-- Stop parts from moving, and capture the initial state of the parts
	for _, Part in pairs(Selection.Items) do
		InitialState[Part] = { Anchored = Part.Anchored, CanCollide = Part.CanCollide, CFrame = Part.CFrame };
		Part.Anchored = true;
		Part.CanCollide = false;
		InitialState[Part].Joints = Core.PreserveJoints(Part, PartIndex);
		Part:BreakJoints();
		Part.Velocity = Vector3.new();
		Part.RotVelocity = Vector3.new();
	end;

	return InitialState;
end;

function GetIncrementMultiple(Number, Increment)

	-- Get how far the actual distance is from a multiple of our increment
	local MultipleDifference = Number % Increment;

	-- Identify the closest lower and upper multiples of the increment
	local LowerMultiple = Number - MultipleDifference;
	local UpperMultiple = Number - MultipleDifference + Increment;

	-- Calculate to which of the two multiples we're closer
	local LowerMultipleProximity = math.abs(Number - LowerMultiple);
	local UpperMultipleProximity = math.abs(Number - UpperMultiple);

	-- Use the closest multiple of our increment as the distance moved
	if LowerMultipleProximity <= UpperMultipleProximity then
		Number = LowerMultiple;
	else
		Number = UpperMultiple;
	end;

	return Number;
end;
