-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- @CloneTrooper1019, 2018
-- ClickToMove
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constants
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

local ContextActionService = game:GetService("ContextActionService")
local GuiService = game:GetService("GuiService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local TeleportService = game:GetService("TeleportService")
local UserInputService = game:GetService("UserInputService")

local IS_TOUCH = UserInputService.TouchEnabled

local ICON_IDLE  = "rbxassetid://334630296"
local ICON_HOVER = "rbxassetid://1000000"
local ICON_CLICK = "rbxasset://textures/DragCursor.png"

local DISK_OFFSET = CFrame.Angles(math.pi / 2,0,0)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Character Listener
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

local player = game.Players.LocalPlayer
local character, humanoid

local function onCharacterAdded(char)
	humanoid = char:WaitForChild("Humanoid")
	character = char
end

if player.Character then
	onCharacterAdded(player.Character)
end

player.CharacterAdded:Connect(onCharacterAdded)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Gui Focus
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

local isMouseHoveringUi = false

local function onInputChanged(input, gameProcessed)
	local inputType = input.UserInputType.Name
	
	if inputType == "MouseMovement" then
		isMouseHoveringUi = gameProcessed
	end
end

local function isGuiFocused()
	return isMouseHoveringUi or GuiService.SelectedObject ~= nil
end

UserInputService.InputChanged:Connect(onInputChanged)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Movement Goal
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

local currentGoal, moveSignal

local function findAngleBetweenXZVectors(vec2, vec1)
	return math.atan2(vec1.X * vec2.Z - vec1.Z * vec2.X, vec1.X * vec2.X + vec1.Z * vec2.Z)
end

local function isFinite(num)
	return num == num and num ~= 1/0 and num ~= -1/0
end

local function rotateCameraTowardsGoal(dt)
	local camera = workspace.CurrentCamera
	
	if camera then
		local cf = camera.CFrame
		local focus = camera.Focus
						
		local desiredAngle = CFrame.new(cf.Position, currentGoal).lookVector
		local currentAngle = cf.LookVector
		
		local angleBetween = findAngleBetweenXZVectors(desiredAngle, currentAngle)
		
		if isFinite(angleBetween) then
			local abs = math.abs(angleBetween)
			local sign = math.sign(angleBetween)
			local rotation = math.min(dt * .6, abs)
			
			local cfLocal = focus:toObjectSpace(cf)
			camera.CFrame = focus * CFrame.Angles(0, -rotation * sign, 0) * cfLocal
		end
	end
end

local function finishGoal()
	if currentGoal then
		currentGoal = nil
	end

	if moveSignal then
		moveSignal:Disconnect()
		moveSignal = nil
	end
end

local function clickToMove(goal)
	finishGoal()
	currentGoal = goal
	
	moveSignal = humanoid.MoveToFinished:Connect(finishGoal)
	
	humanoid:Move(Vector3.new(1,1,1))
	humanoid:MoveTo(currentGoal)
end

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Green Disk
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

local mouse = player:GetMouse()
local mouseIcon = script.Parent 
mouse.TargetFilter = workspace.CurrentCamera

local lastTarget
local lastTargetCanClick = false

local adorn = Instance.new("Part", script)

local disk = Instance.new("CylinderHandleAdornment")
disk.Name = "Disk"
disk.Color3 = Color3.new(0,1,0)
disk.Radius = 1
disk.Height = 0.2
disk.Visible = false
disk.Adornee = adorn
disk.Parent = script

local goalDisk = disk:Clone()
goalDisk.Name = "Goal"
goalDisk.Parent = script

local function hasTool()
	if character then
		return character:FindFirstChildOfClass("Tool") ~= nil
	end
	
	return false
end

local function isFirstPerson()
	if character then
		local head = character:FindFirstChild("Head")
		if head then
			return head.LocalTransparencyModifier == 1
		end
	end

	return false
end

local function canClickTarget()
	local target = mouse.Target

	if target then
		if target ~= lastTarget then
			local canClick = false
			local clickDetector = target:FindFirstChildOfClass("ClickDetector")
			
			if clickDetector then
				local dist = player:DistanceFromCharacter(target.Position)
				if dist <= clickDetector.MaxActivationDistance then
					canClick = true
				end
			end
			
			lastTarget = target
			lastTargetCanClick = canClick
		end
		
		return lastTargetCanClick
	end
end

local function canRenderDisk(rendering)
	if not TeleportService:GetTeleportSetting("ClickToMove") then
		return false
	end
	
	if rendering and IS_TOUCH then
		return false
	end
	
	if humanoid then
		local movement = humanoid.MoveDirection
		if movement.Magnitude == 0 then
			local pos = mouse.Hit.Position
			local dist = player:DistanceFromCharacter(pos)
			
			if dist < 32 then
				local blockers = {hasTool, isFirstPerson, canClickTarget, isGuiFocused}
				
				for _,blocker in pairs(blockers) do
					if blocker() then
						return false
					end
				end
				
				return true
			end
		else
			finishGoal()
		end
	end
	
	return false
end

local function render3dAdorn(dt)
	local dt = math.min(0.1, dt)
	disk.Visible = canRenderDisk(true)
	
	if disk.Visible then
		disk.CFrame = CFrame.new(mouse.Hit.Position) * DISK_OFFSET
		mouseIcon.Image = ICON_HOVER
	elseif canClickTarget() then
		mouseIcon.Image = ICON_CLICK
	elseif not hasTool() then
		mouseIcon.Image = ICON_IDLE
	end
	
	if currentGoal then
		goalDisk.Visible = true
		goalDisk.CFrame = CFrame.new(currentGoal) * DISK_OFFSET
		rotateCameraTowardsGoal(dt)
	else
		goalDisk.Visible = false
	end
end

RunService.Heartbeat:Connect(render3dAdorn)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Click Action
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

local function onInputBegan(input, gameProcessed)
	local goal = mouse.Hit.Position
	
	if not gameProcessed and canRenderDisk() and humanoid then	
		local name = input.UserInputType.Name
		
		if name == "MouseButton1" then
			clickToMove(goal)
		elseif name == "Touch" then
			wait(.1)
			if input.UserInputState == Enum.UserInputState.End then
				clickToMove(goal)
			end
		elseif name == "Gamepad1" then
			if input.KeyCode == Enum.KeyCode.ButtonR2 then
				clickToMove(goal)
			end
		end
	end
end

UserInputService.InputBegan:Connect(onInputBegan)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- OBLITERATE the invasive click to move mode that Roblox provides
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

pcall(function ()
	if IS_TOUCH then
		local playerScripts = player:WaitForChild("PlayerScripts")
		local no = function () end
		
		spawn(function ()
			local controlScript = playerScripts:WaitForChild("ControlScript", 86400)
			if controlScript then
				local masterControl = controlScript:WaitForChild("MasterControl")
				
				local clickToMove = masterControl:WaitForChild("ClickToMoveController")
				clickToMove = require(clickToMove)
				
				clickToMove:Disable()
				clickToMove.Enable = no
			end
		end)
		
		spawn(function ()
			local playerModule = playerScripts:WaitForChild("PlayerModule", 86400)
			if playerModule then
				local controlModule = playerModule:WaitForChild("ControlModule")
				
				local clickToMove = controlModule:WaitForChild("ClickToMoveController")
				clickToMove = require(clickToMove)
				
				clickToMove:Stop()
				clickToMove.Enable = no
			end
		end)
	end
end)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
