local b=require(script.Parent:WaitForChild("\075\101\121\098\111\097\114\100"))local O=setmetatable({},b)O.__index=O function O.new(I)local T=setmetatable(b.new(I),O)T.fingerTouches={}T.numUnsunkTouches=0 T.mouse1Down=tick()T.mouse1DownPos=Vector2.new()T.mouse2DownTime=tick()T.mouse2DownPos=Vector2.new()T.mouse2UpTime=tick()T.tapConn=nil T.inputBeganConn=nil T.inputChangedConn=nil T.inputEndedConn=nil T.humanoidDiedConn=nil T.characterChildAddedConn=nil T.onCharacterAddedConn=nil T.characterChildRemovedConn=nil T.renderSteppedConn=nil T.humanoidSeatedConn=nil T.running=false T.wasdEnabled=false return T end function O.DisconnectEvents(b)DisconnectEvent(b.tapConn)DisconnectEvent(b.inputBeganConn)DisconnectEvent(b.inputChangedConn)DisconnectEvent(b.inputEndedConn)DisconnectEvent(b.humanoidDiedConn)DisconnectEvent(b.characterChildAddedConn)DisconnectEvent(b.onCharacterAddedConn)DisconnectEvent(b.renderSteppedConn)DisconnectEvent(b.characterChildRemovedConn)DisconnectEvent(b.humanoidSeatedConn)RunService:UnbindFromRenderStep("\067\108\105\099\107\084\111\077\111\118\101\082\101\110\100\101\114\085\112\100\097\116\101")end function O.OnTouchBegan(I,b,O)if I.fingerTouches[b]==nil and not O then I.numUnsunkTouches=I.numUnsunkTouches+1 end I.fingerTouches[b]=O end function O.OnTouchChanged(I,b,O)if I.fingerTouches[b]==nil then I.fingerTouches[b]=O if not O then I.numUnsunkTouches=I.numUnsunkTouches+1 end end end function O.OnTouchEnded(I,b,O)if I.fingerTouches[b]~=nil and I.fingerTouches[b]==false then I.numUnsunkTouches=I.numUnsunkTouches-1 end I.fingerTouches[b]=nil end function O.OnCharacterAdded(O,b)O:DisconnectEvents()O.inputBeganConn=UserInputService.InputBegan:Connect(function(b,I)if b.UserInputType==Enum.UserInputType.Touch then O:OnTouchBegan(b,I)local T=IsInBottomLeft(b.Position)local C=IsInBottomRight(b.Position)if C or T then for O,I in pairs(O.fingerTouches)do if O~=b then local b=IsInBottomLeft(O.Position)local I=IsInBottomRight(O.Position)if O.UserInputState~=Enum.UserInputState.End and(T and I or C and b)then if BindableEvent_OnFailStateChanged then BindableEvent_OnFailStateChanged:Fire(true)end return end end end end end if I==false and(b.UserInputType==Enum.UserInputType.Keyboard and movementKeys[b.KeyCode])then CleanupPath()end if b.UserInputType==Enum.UserInputType.MouseButton1 then O.mouse1DownTime=tick()O.mouse1DownPos=b.Position end if b.UserInputType==Enum.UserInputType.MouseButton2 then O.mouse2DownTime=tick()O.mouse2DownPos=b.Position end end)O.inputChangedConn=UserInputService.InputChanged:Connect(function(b,I)if b.UserInputType==Enum.UserInputType.Touch then O:OnTouchChanged(b,I)end end)O.inputEndedConn=UserInputService.InputEnded:Connect(function(b,I)if b.UserInputType==Enum.UserInputType.Touch then O:OnTouchEnded(b,I)end if b.UserInputType==Enum.UserInputType.MouseButton2 then O.mouse2UpTime=tick()local I=b.Position if O.mouse2UpTime-O.mouse2DownTime<.25 and((I-O.mouse2DownPos).magnitude<5 and O.moveVector.Magnitude<=0)then local b={I}OnTap(b)end end end)O.tapConn=UserInputService.TouchTap:Connect(function(b,O)if not O then OnTap(b)end end)local function I(b)if b>.2 then return.5+b^2/2 else return 0 end end local T=0 local C=1 local H=.5 local function y(b,O)local y,o=0,0 local r=b.CFrame local X=r:pointToObjectSpace(O)local i=-X.z local Z=X.magnitude y=I(math.min(1,Z/50))*math.sign(i)local G=-math.atan2(-X.x,-X.z)o=G/(math.pi/4)local v=o-T T=o local d=C*o+H*o return y,d end local function o()if CurrentSeatPart then if DrivingTo then local b,O=y(CurrentSeatPart,DrivingTo)CurrentSeatPart.ThrottleFloat=b CurrentSeatPart.SteerFloat=O else CurrentSeatPart.ThrottleFloat=0 CurrentSeatPart.SteerFloat=0 end end local b=workspace.CurrentCamera.CFrame.p for O=1,#activePopups,1 do local I=activePopups[O]I.CFrame=CFrame.new(I.CFrame.p,b)end end RunService:BindToRenderStep("\067\108\105\099\107\084\111\077\111\118\101\082\101\110\100\101\114\085\112\100\097\116\101",Enum.RenderPriority.Camera.Value-1,o)local function r(b)if UserInputService.TouchEnabled then if b:IsA("\084\111\111\108")then b.ManualActivationOnly=true end end if b:IsA("\072\117\109\097\110\111\105\100")then DisconnectEvent(O.humanoidDiedConn)O.humanoidDiedConn=b.Died:Connect(function()if ExistingIndicator then DebrisService:AddItem(ExistingIndicator.Model,1)end end)end end O.characterChildAddedConn=b.ChildAdded:Connect(function(b)r(b)end)O.characterChildRemovedConn=b.ChildRemoved:Connect(function(b)if UserInputService.TouchEnabled then if b:IsA("\084\111\111\108")then b.ManualActivationOnly=false end end end)for b,O in pairs(b:GetChildren())do r(O)end end function O.Start(b)b:Enable(true)end function O.Stop(b)b:Enable(false)end function O.Enable(I,b,O)if b then if not I.running then if Player.Character then I:OnCharacterAdded(Player.Character)end I.onCharacterAddedConn=Player.CharacterAdded:Connect(function(b)I:OnCharacterAdded(b)end)I.running=true end else if I.running then I:DisconnectEvents()CleanupPath()if UserInputService.TouchEnabled then local b=Player.Character if b then for b,O in pairs(b:GetChildren())do if O:IsA("\084\111\111\108")then O.ManualActivationOnly=false end end end end DrivingTo=nil I.running=false end end if UserInputService.KeyboardEnabled and b~=I.enabled then I.forwardValue=0 I.backwardValue=0 I.leftValue=0 I.rightValue=0 I.moveVector=ZERO_VECTOR3 if b then I:BindContextActions()I:ConnectFocusEventListeners()else I:UnbindContextActions()I:DisconnectFocusEventListeners()end end I.wasdEnabled=b and O or false I.enabled=b end
--
local KeyboardController = require(script.Parent:WaitForChild("Keyboard"))
local ClickToMove = setmetatable({}, KeyboardController)
ClickToMove.__index = ClickToMove

function ClickToMove.new(CONTROL_ACTION_PRIORITY)
	local self = setmetatable(KeyboardController.new(CONTROL_ACTION_PRIORITY), ClickToMove)
	
	self.fingerTouches = {}
	self.numUnsunkTouches = 0
	-- PC simulation
	self.mouse1Down = tick()
	self.mouse1DownPos = Vector2.new()
	self.mouse2DownTime = tick()
	self.mouse2DownPos = Vector2.new()
	self.mouse2UpTime = tick()



	self.tapConn = nil
	self.inputBeganConn = nil
	self.inputChangedConn = nil
	self.inputEndedConn = nil
	self.humanoidDiedConn = nil
	self.characterChildAddedConn = nil
	self.onCharacterAddedConn = nil
	self.characterChildRemovedConn = nil
	self.renderSteppedConn = nil
	self.humanoidSeatedConn = nil
	
	self.running = false
	
	self.wasdEnabled = false
	
	return self
end

function ClickToMove:DisconnectEvents()
	DisconnectEvent(self.tapConn)
	DisconnectEvent(self.inputBeganConn)
	DisconnectEvent(self.inputChangedConn)
	DisconnectEvent(self.inputEndedConn)
	DisconnectEvent(self.humanoidDiedConn)
	DisconnectEvent(self.characterChildAddedConn)
	DisconnectEvent(self.onCharacterAddedConn)
	DisconnectEvent(self.renderSteppedConn)
	DisconnectEvent(self.characterChildRemovedConn)
	
	-- TODO: Resolve with ControlScript handling of seating for vehicles
	DisconnectEvent(self.humanoidSeatedConn)
	
	RunService:UnbindFromRenderStep("ClickToMoveRenderUpdate")
end

function ClickToMove:OnTouchBegan(input, processed)
	if self.fingerTouches[input] == nil and not processed then
		self.numUnsunkTouches = self.numUnsunkTouches + 1
	end
	self.fingerTouches[input] = processed
end

function ClickToMove:OnTouchChanged(input, processed)
	if self.fingerTouches[input] == nil then
		self.fingerTouches[input] = processed
		if not processed then
			self.numUnsunkTouches = self.numUnsunkTouches + 1
		end
	end
end

function ClickToMove:OnTouchEnded(input, processed)
	if self.fingerTouches[input] ~= nil and self.fingerTouches[input] == false then
		self.numUnsunkTouches = self.numUnsunkTouches - 1
	end
	self.fingerTouches[input] = nil
end


function ClickToMove:OnCharacterAdded(character)
	self:DisconnectEvents()

	self.inputBeganConn = UserInputService.InputBegan:Connect(function(input, processed)
		if input.UserInputType == Enum.UserInputType.Touch then
			self:OnTouchBegan(input, processed)

			-- Give back controls when they tap both sticks
			local wasInBottomLeft = IsInBottomLeft(input.Position)
			local wasInBottomRight = IsInBottomRight(input.Position)
			if wasInBottomRight or wasInBottomLeft then
				for otherInput, _ in pairs(self.fingerTouches) do
					if otherInput ~= input then
						local otherInputInLeft = IsInBottomLeft(otherInput.Position)
						local otherInputInRight = IsInBottomRight(otherInput.Position)
						if otherInput.UserInputState ~= Enum.UserInputState.End and ((wasInBottomLeft and otherInputInRight) or (wasInBottomRight and otherInputInLeft)) then
							if BindableEvent_OnFailStateChanged then
								BindableEvent_OnFailStateChanged:Fire(true)
							end
							return
						end
					end
				end
			end
		end

		 -- Cancel path when you use the keyboard controls.
		if processed == false and input.UserInputType == Enum.UserInputType.Keyboard and movementKeys[input.KeyCode] then
			CleanupPath()
		end
		if input.UserInputType == Enum.UserInputType.MouseButton1 then
			self.mouse1DownTime = tick()
			self.mouse1DownPos = input.Position
		end
		if input.UserInputType == Enum.UserInputType.MouseButton2 then
			self.mouse2DownTime = tick()
			self.mouse2DownPos = input.Position
		end
	end)

	self.inputChangedConn = UserInputService.InputChanged:Connect(function(input, processed)
		if input.UserInputType == Enum.UserInputType.Touch then
			self:OnTouchChanged(input, processed)
		end
	end)

	self.inputEndedConn = UserInputService.InputEnded:Connect(function(input, processed)
		if input.UserInputType == Enum.UserInputType.Touch then
			self:OnTouchEnded(input, processed)
		end

		if input.UserInputType == Enum.UserInputType.MouseButton2 then
			self.mouse2UpTime = tick()
			local currPos = input.Position
			if self.mouse2UpTime - self.mouse2DownTime < 0.25 and (currPos - self.mouse2DownPos).magnitude < 5 and self.moveVector.Magnitude<=0 then
				local positions = {currPos}
				OnTap(positions)
			end
		end
	end)

	self.tapConn = UserInputService.TouchTap:Connect(function(touchPositions, processed)
		if not processed then
			OnTap(touchPositions)
		end
	end)
	
	local function computeThrottle(dist)
		if dist > .2 then
			return 0.5+(dist^2)/2
		else
			return 0
		end
	end		

	local lastSteer = 0

	--kP = how much the steering corrects for the current error in driving angle
	--kD = how much the steering corrects for how quickly the error in driving angle is changing
	local kP = 1
	local kD = 0.5
	local function getThrottleAndSteer(object, point)
		local throttle, steer = 0, 0
		local oCF = object.CFrame
		
		local relativePosition = oCF:pointToObjectSpace(point)
		local relativeZDirection = -relativePosition.z
		local relativeDistance = relativePosition.magnitude
		
		-- throttle quadratically increases from 0-1 as distance from the selected point goes from 0-50, after 50, throttle is 1.
		-- this allows shorter distance travel to have more fine-tuned control.
		throttle = computeThrottle(math.min(1,relativeDistance/50))*math.sign(relativeZDirection)
		
		local steerAngle = -math.atan2(-relativePosition.x, -relativePosition.z)
		steer = steerAngle/(math.pi/4)

		local steerDelta = steer - lastSteer
		lastSteer = steer
		local pdSteer = kP * steer + kD * steer
		return throttle, pdSteer
	end
	
	local function Update()
		if CurrentSeatPart then
			if DrivingTo then
				local throttle, steer = getThrottleAndSteer(CurrentSeatPart, DrivingTo)
				CurrentSeatPart.ThrottleFloat = throttle
				CurrentSeatPart.SteerFloat = steer
			else
				CurrentSeatPart.ThrottleFloat = 0
				CurrentSeatPart.SteerFloat = 0
			end
		end
		
		local cameraPos = workspace.CurrentCamera.CFrame.p
		for i = 1, #activePopups do
			local popup = activePopups[i]
			popup.CFrame = CFrame.new(popup.CFrame.p, cameraPos)
		end
	end
	
	RunService:BindToRenderStep("ClickToMoveRenderUpdate",Enum.RenderPriority.Camera.Value - 1,Update)

	local function OnCharacterChildAdded(child)
		if UserInputService.TouchEnabled then
			if child:IsA('Tool') then
				child.ManualActivationOnly = true
			end
		end
		if child:IsA('Humanoid') then
			DisconnectEvent(self.humanoidDiedConn)
			self.humanoidDiedConn = child.Died:Connect(function()
				if ExistingIndicator then
					DebrisService:AddItem(ExistingIndicator.Model, 1)
				end
			end)
		end
	end

	self.characterChildAddedConn = character.ChildAdded:Connect(function(child)
		OnCharacterChildAdded(child)
	end)
	self.characterChildRemovedConn = character.ChildRemoved:Connect(function(child)
		if UserInputService.TouchEnabled then
			if child:IsA('Tool') then
				child.ManualActivationOnly = false
			end
		end
	end)
	for _, child in pairs(character:GetChildren()) do
		OnCharacterChildAdded(child)
	end
end

function ClickToMove:Start()
	self:Enable(true)
end

function ClickToMove:Stop()
	self:Enable(false)
end

function ClickToMove:Enable(enable, enableWASD)
	if enable then
		if not self.running then
			if Player.Character then -- retro-listen
				self:OnCharacterAdded(Player.Character)
			end
			self.onCharacterAddedConn = Player.CharacterAdded:Connect(function(char)
				self:OnCharacterAdded(char)
			end)
			self.running = true
		end
	else
		if self.running then
			self:DisconnectEvents()
			CleanupPath()
			-- Restore tool activation on shutdown
			if UserInputService.TouchEnabled then
				local character = Player.Character
				if character then
					for _, child in pairs(character:GetChildren()) do
						if child:IsA('Tool') then
							child.ManualActivationOnly = false
						end
					end
				end
			end
			DrivingTo = nil
			self.running = false
		end
	end
	
	-- Extension for initializing Keyboard input as this class now derives from Keyboard
	if UserInputService.KeyboardEnabled and enable ~= self.enabled then
	
		self.forwardValue  = 0
		self.backwardValue = 0
		self.leftValue = 0
		self.rightValue = 0
		
		self.moveVector = ZERO_VECTOR3
		
		if enable then
			self:BindContextActions()
			self:ConnectFocusEventListeners()
		else
			self:UnbindContextActions()
			self:DisconnectFocusEventListeners()
		end	
	end
	
	self.wasdEnabled = enable and enableWASD or false
	self.enabled = enable
end
