local N={}function RotateTool.Equip()SetPivot(RotateTool.Pivot)ShowUI()BindShortcutKeys()end function RotateTool.Unequip()HideUI()HideHandles()ClearConnections()BoundingBox.ClearBoundingBox()SnapTracking.StopTracking()end function ClearConnections()for k,z in pairs(N)do z:disconnect()N[k]=nil end end function ClearConnection(k)local z=N[k]if N[k]then z:disconnect()N[k]=nil end end function ShowUI()if RotateTool.UI then RotateTool.UI.Visible=true UIUpdater=Support.ScheduleRecurringTask(UpdateUI,.1)return end RotateTool.UI=Core.Tool.Interfaces.BTRotateToolGUI:Clone()RotateTool.UI.Parent=Core.UI RotateTool.UI.Visible=true local N=RotateTool.UI.PivotOption N.Center.Button.MouseButton1Down:connect(function()SetPivot("\067\101\110\116\101\114")end)N.Local.Button.MouseButton1Down:connect(function()SetPivot("\076\111\099\097\108")end)N.Last.Button.MouseButton1Down:connect(function()SetPivot("\076\097\115\116")end)local k=RotateTool.UI.IncrementOption.Increment.TextBox k.FocusLost:connect(function(N)RotateTool.Increment=tonumber(k.Text)or RotateTool.Increment k.Text=Support.Round(RotateTool.Increment,4)end)local z=RotateTool.UI.Info.RotationInfo.X.TextBox local L=RotateTool.UI.Info.RotationInfo.Y.TextBox local l=RotateTool.UI.Info.RotationInfo.Z.TextBox z.FocusLost:connect(function(N)local k=tonumber(z.Text)if k then SetAxisAngle("\088",k)end end)L.FocusLost:connect(function(N)local k=tonumber(L.Text)if k then SetAxisAngle("\089",k)end end)l.FocusLost:connect(function(N)local k=tonumber(l.Text)if k then SetAxisAngle("\090",k)end end)UIUpdater=Support.ScheduleRecurringTask(UpdateUI,.1)end function HideUI()if not RotateTool.UI then return end RotateTool.UI.Visible=false UIUpdater:Stop()end function UpdateUI()if not RotateTool.UI then return end if#Selection.Items==0 then RotateTool.UI.Info.Visible=false RotateTool.UI.Size=UDim2.new(0,245,0,90)return else RotateTool.UI.Info.Visible=true RotateTool.UI.Size=UDim2.new(0,245,0,150)end local N,k,z={},{},{}for L,l in pairs(Selection.Items)do table.insert(N,Support.Round(l.Orientation.X,3))table.insert(k,Support.Round(l.Orientation.Y,3))table.insert(z,Support.Round(l.Orientation.Z,3))end local L=Support.IdentifyCommonItem(N)local l=Support.IdentifyCommonItem(k)local K=Support.IdentifyCommonItem(z)local w=RotateTool.UI.Info.RotationInfo.X.TextBox local s=RotateTool.UI.Info.RotationInfo.Y.TextBox local M=RotateTool.UI.Info.RotationInfo.Z.TextBox if not w:IsFocused()then w.Text=L or"\042"end if not s:IsFocused()then s.Text=l or"\042"end if not M:IsFocused()then M.Text=K or"\042"end end function SetPivot(N)RotateTool.Pivot=N if RotateTool.UI then Core.ToggleSwitch(N,RotateTool.UI.PivotOption)end BoundingBox.ClearBoundingBox()if N=="\067\101\110\116\101\114"then BoundingBox.StartBoundingBox(AttachHandles)elseif N=="\076\111\099\097\108"then AttachHandles(Selection.Focus,true)elseif N=="\076\097\115\116"then AttachHandles(CustomPivotPoint and Handles.Adornee or Selection.Focus,true)end end function AttachHandles(k,z)if z and not N.AutofocusHandle then N.AutofocusHandle=Selection.FocusChanged:connect(function()AttachHandles(Selection.Focus,true)end)elseif not z and N.AutofocusHandle then ClearConnection("\065\117\116\111\102\111\099\117\115\072\097\110\100\108\101")end if Handles then Handles.Adornee=k Handles.Visible=true Handles.Parent=k and Core.UIContainer or nil return end Handles=(Create("\065\114\099\072\097\110\100\108\101\115"))({Name="\066\084\082\111\116\097\116\105\111\110\072\097\110\100\108\101\115",Color=RotateTool.Color;Parent=Core.UIContainer;Adornee=k})local L Handles.MouseButton1Down:connect(function()Core.Targeting.CancelSelecting()HandleRotating=true if BoundingBox.GetBoundingBox()then InitialExtentsSize,InitialExtentsCFrame=BoundingBox.CalculateExtents(Core.Selection.Items,BoundingBox.StaticExtents)BoundingBox.PauseMonitoring()end InitialState=PreparePartsForRotating()TrackChange()if Core.Mode=="\084\111\111\108"then L=Security.GetPermissions(Security.GetSelectionAreas(Selection.Items),Core.Player)end if RotateTool.Pivot=="\067\101\110\116\101\114"then PivotPoint=(BoundingBox.GetBoundingBox()).CFrame elseif RotateTool.Pivot=="\076\097\115\116"and not CustomPivotPoint then PivotPoint=InitialState[Selection.Focus].CFrame end end)Handles.MouseDrag:connect(function(N,k)if not HandleRotating then return end k=math.deg(k)k=GetIncrementMultiple(k,RotateTool.Increment)%360 local z=GetHandleDisplayDelta(k)RotatePartsAroundPivot(RotateTool.Pivot,PivotPoint,N,k,InitialState)if Core.Mode=="\084\111\111\108"and Security.ArePartsViolatingAreas(Selection.Items,Core.Player,false,L)then for N,k in pairs(InitialState)do N.CFrame=k.CFrame end z=0 end if RotateTool.UI then RotateTool.UI.Changes.Text.Text="\114\111\116\097\116\101\100 "..(z.." \100\101\103\114\101\101\115")end end)end

local Connections = {};

function RotateTool.Equip()
	-- Enables the tool's equipped functionality

	-- Set our current pivot mode
	SetPivot(RotateTool.Pivot);

	-- Start up our interface
	ShowUI();
	BindShortcutKeys();

end;

function RotateTool.Unequip()
	-- Disables the tool's equipped functionality

	-- Clear unnecessary resources
	HideUI();
	HideHandles();
	ClearConnections();
	BoundingBox.ClearBoundingBox();
	SnapTracking.StopTracking();

end;

function ClearConnections()
	-- Clears out temporary connections

	for ConnectionKey, Connection in pairs(Connections) do
		Connection:disconnect();
		Connections[ConnectionKey] = nil;
	end;

end;

function ClearConnection(ConnectionKey)
	-- Clears the given specific connection

	local Connection = Connections[ConnectionKey];

	-- Disconnect the connection if it exists
	if Connections[ConnectionKey] then
		Connection:disconnect();
		Connections[ConnectionKey] = nil;
	end;

end;

function ShowUI()
	-- Creates and reveals the UI

	-- Reveal UI if already created
	if RotateTool.UI then

		-- Reveal the UI
		RotateTool.UI.Visible = true;

		-- Update the UI every 0.1 seconds
		UIUpdater = Support.ScheduleRecurringTask(UpdateUI, 0.1);

		-- Skip UI creation
		return;

	end;

	-- Create the UI
	RotateTool.UI = Core.Tool.Interfaces.BTRotateToolGUI:Clone();
	RotateTool.UI.Parent = Core.UI;
	RotateTool.UI.Visible = true;

	-- Add functionality to the pivot option switch
	local PivotSwitch = RotateTool.UI.PivotOption;
	PivotSwitch.Center.Button.MouseButton1Down:connect(function ()
		SetPivot('Center');
	end);
	PivotSwitch.Local.Button.MouseButton1Down:connect(function ()
		SetPivot('Local');
	end);
	PivotSwitch.Last.Button.MouseButton1Down:connect(function ()
		SetPivot('Last');
	end);

	-- Add functionality to the increment input
	local IncrementInput = RotateTool.UI.IncrementOption.Increment.TextBox;
	IncrementInput.FocusLost:connect(function (EnterPressed)
		RotateTool.Increment = tonumber(IncrementInput.Text) or RotateTool.Increment;
		IncrementInput.Text = Support.Round(RotateTool.Increment, 4);
	end);

	-- Add functionality to the rotation inputs
	local XInput = RotateTool.UI.Info.RotationInfo.X.TextBox;
	local YInput = RotateTool.UI.Info.RotationInfo.Y.TextBox;
	local ZInput = RotateTool.UI.Info.RotationInfo.Z.TextBox;
	XInput.FocusLost:connect(function (EnterPressed)
		local NewAngle = tonumber(XInput.Text);
		if NewAngle then
			SetAxisAngle('X', NewAngle);
		end;
	end);
	YInput.FocusLost:connect(function (EnterPressed)
		local NewAngle = tonumber(YInput.Text);
		if NewAngle then
			SetAxisAngle('Y', NewAngle);
		end;
	end);
	ZInput.FocusLost:connect(function (EnterPressed)
		local NewAngle = tonumber(ZInput.Text);
		if NewAngle then
			SetAxisAngle('Z', NewAngle);
		end;
	end);

	-- Update the UI every 0.1 seconds
	UIUpdater = Support.ScheduleRecurringTask(UpdateUI, 0.1);

end;

function HideUI()
	-- Hides the tool UI

	-- Make sure there's a UI
	if not RotateTool.UI then
		return;
	end;

	-- Hide the UI
	RotateTool.UI.Visible = false;

	-- Stop updating the UI
	UIUpdater:Stop();

end;

function UpdateUI()
	-- Updates information on the UI

	-- Make sure the UI's on
	if not RotateTool.UI then
		return;
	end;

	-- Only show and calculate selection info if it's not empty
	if #Selection.Items == 0 then
		RotateTool.UI.Info.Visible = false;
		RotateTool.UI.Size = UDim2.new(0, 245, 0, 90);
		return;
	else
		RotateTool.UI.Info.Visible = true;
		RotateTool.UI.Size = UDim2.new(0, 245, 0, 150);
	end;

	-----------------------------------------
	-- Update the size information indicators
	-----------------------------------------

	-- Identify common angles across axes
	local XVariations, YVariations, ZVariations = {}, {}, {};
	for _, Part in pairs(Selection.Items) do
		table.insert(XVariations, Support.Round(Part.Orientation.X, 3));
		table.insert(YVariations, Support.Round(Part.Orientation.Y, 3));
		table.insert(ZVariations, Support.Round(Part.Orientation.Z, 3));
	end;
	local CommonX = Support.IdentifyCommonItem(XVariations);
	local CommonY = Support.IdentifyCommonItem(YVariations);
	local CommonZ = Support.IdentifyCommonItem(ZVariations);

	-- Shortcuts to indicators
	local XIndicator = RotateTool.UI.Info.RotationInfo.X.TextBox;
	local YIndicator = RotateTool.UI.Info.RotationInfo.Y.TextBox;
	local ZIndicator = RotateTool.UI.Info.RotationInfo.Z.TextBox;

	-- Update each indicator if it's not currently being edited
	if not XIndicator:IsFocused() then
		XIndicator.Text = CommonX or '*';
	end;
	if not YIndicator:IsFocused() then
		YIndicator.Text = CommonY or '*';
	end;
	if not ZIndicator:IsFocused() then
		ZIndicator.Text = CommonZ or '*';
	end;

end;

function SetPivot(PivotMode)
	-- Sets the given rotation pivot mode

	-- Update setting
	RotateTool.Pivot = PivotMode;

	-- Update the UI switch
	if RotateTool.UI then
		Core.ToggleSwitch(PivotMode, RotateTool.UI.PivotOption);
	end;

	-- Disable any unnecessary bounding boxes
	BoundingBox.ClearBoundingBox();

	-- For center mode, use bounding box handles
	if PivotMode == 'Center' then
		BoundingBox.StartBoundingBox(AttachHandles);

	-- For local mode, use focused part handles
	elseif PivotMode == 'Local' then
		AttachHandles(Selection.Focus, true); 

	-- For last mode, use focused part handles
	elseif PivotMode == 'Last' then
		AttachHandles(CustomPivotPoint and Handles.Adornee or Selection.Focus, true);
	end;

end;

function AttachHandles(Part, Autofocus)
	-- Creates and attaches handles to `Part`, and optionally automatically attaches to the focused part

	-- Enable autofocus if requested and not already on
	if Autofocus and not Connections.AutofocusHandle then
		Connections.AutofocusHandle = Selection.FocusChanged:connect(function ()
			AttachHandles(Selection.Focus, true);
		end);

	-- Disable autofocus if not requested and on
	elseif not Autofocus and Connections.AutofocusHandle then
		ClearConnection 'AutofocusHandle';
	end;

	-- Just attach and show the handles if they already exist
	if Handles then
		Handles.Adornee = Part;
		Handles.Visible = true;
		Handles.Parent = Part and Core.UIContainer or nil;
		return;
	end;

	-- Create the handles
	Handles = Create 'ArcHandles' {
		Name = 'BTRotationHandles';
		Color = RotateTool.Color;
		Parent = Core.UIContainer;
		Adornee = Part;
	};

	--------------------------------------------------------
	-- Prepare for rotating parts when the handle is clicked
	--------------------------------------------------------

	local AreaPermissions;

	Handles.MouseButton1Down:connect(function ()

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

		-- Indicate rotating via handle
		HandleRotating = true;

		-- Freeze bounding box extents while rotating
		if BoundingBox.GetBoundingBox() then
			InitialExtentsSize, InitialExtentsCFrame = BoundingBox.CalculateExtents(Core.Selection.Items, BoundingBox.StaticExtents);
			BoundingBox.PauseMonitoring();
		end;

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

		-- Track the change
		TrackChange();

		-- Cache area permissions information
		if Core.Mode == 'Tool' then
			AreaPermissions = Security.GetPermissions(Security.GetSelectionAreas(Selection.Items), Core.Player);
		end;

		-- Set the pivot point to the center of the selection if in Center mode
		if RotateTool.Pivot == 'Center' then
			PivotPoint = BoundingBox.GetBoundingBox().CFrame;

		-- 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;

	end);

	------------------------------------------
	-- Update parts when the handles are moved
	------------------------------------------

	Handles.MouseDrag:connect(function (Axis, Rotation)

		-- Only rotate if handle is enabled
		if not HandleRotating then
			return;
		end;

		-- Turn the rotation amount into degrees
		Rotation = math.deg(Rotation);

		-- Calculate the increment-aligned rotation amount
		Rotation = GetIncrementMultiple(Rotation, RotateTool.Increment) % 360;

		-- Get displayable rotation delta
		local DisplayedRotation = GetHandleDisplayDelta(Rotation);

		-- Perform the rotation
		RotatePartsAroundPivot(RotateTool.Pivot, PivotPoint, Axis, Rotation, InitialState);

		-- 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;

			-- Reset displayed rotation delta
			DisplayedRotation = 0;
		end;

		-- Update the "degrees rotated" indicator
		if RotateTool.UI then
			RotateTool.UI.Changes.Text.Text = 'rotated ' .. DisplayedRotation .. ' degrees';
		end;

	end);

end;
