--!nonstrict
-- Written By Kip Turner, Copyright Roblox 2014

local newClickToMove = script:FindFirstChild("NewClickToMove")
if newClickToMove then
	return require(newClickToMove) :: any
end

local UIS = game:GetService("UserInputService")
local PathfindingService = game:GetService("PathfindingService")
local PlayerService = game:GetService("Players")
local RunService = game:GetService("RunService")
local DebrisService = game:GetService('Debris')
local ReplicatedStorage = game:GetService('ReplicatedStorage')

local CameraScript = script.Parent
local ClassicCameraModule = require(CameraScript:WaitForChild('RootCamera'):WaitForChild('ClassicCamera'))
local CameraModule: any = nil

local Player = PlayerService.localPlayer
local MyMouse = Player:GetMouse()


local DirectPathEnabled = false
local SHOW_PATH = false

local Y_VECTOR3 = Vector3.new(0, 1, 0)
local XZ_VECTOR3 = Vector3.new(1, 0, 1)
local ZERO_VECTOR3 = Vector3.new(0, 0, 0)
local ZERO_VECTOR2 = Vector2.new(0, 0)

local RayCastIgnoreList = workspace.FindPartOnRayWithIgnoreList
local GetPartsTouchingExtents = workspace.FindPartsInRegion3

local math_min = math.min
local math_max = math.max
local math_pi = math.pi
local math_floor = math.floor
local math_abs = math.abs
local math_deg = math.deg
local math_acos = math.acos
local math_sin = math.sin
local math_atan2 = math.atan2

local Vector3_new = Vector3.new
local Vector2_new = Vector2.new
local CFrame_new = CFrame.new

local CurrentSeatPart = nil
local DrivingTo = nil

-- Bindable for when we want touch emergency controls
-- TODO: Click to move should probably have it's own gui touch controls
-- to manage this.
local BindableEvent_OnFailStateChanged = nil
local BindableEvent_EnableTouchJump = nil
if UIS.TouchEnabled then
	BindableEvent_OnFailStateChanged = Instance.new('BindableEvent')
	BindableEvent_OnFailStateChanged.Name = "OnClickToMoveFailStateChange"
	BindableEvent_EnableTouchJump = Instance.new('BindableEvent')
	BindableEvent_EnableTouchJump.Name = "EnableTouchJump"
	local CameraScript = script.Parent
	local PlayerScripts = CameraScript.Parent
	BindableEvent_OnFailStateChanged.Parent = PlayerScripts
	BindableEvent_EnableTouchJump.Parent = PlayerScripts
end

local function clamp(low, high, num)
	return (num > high and high or num < low and low or num)
end

--------------------------UTIL LIBRARY-------------------------------
local Utility = {}
do
	local Signal = {}

	function Signal.Create()
		local sig = {}

		local mSignaler = Instance.new('BindableEvent')

		local mArgData = nil
		local mArgDataCount = nil

		function sig:fire(...)
			mArgData = {...}
			mArgDataCount = select('#', ...)
			mSignaler:Fire()
		end

		function sig:connect(f)
			if not f then error("connect(nil)", 2) end
			return mSignaler.Event:connect(function()
				f(unpack(mArgData, 1, mArgDataCount))
			end)
		end

		function sig:wait()
			mSignaler.Event:wait()
			assert(mArgData, "Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.")
			return unpack(mArgData, 1, mArgDataCount)
		end

		return sig
	end
	Utility.Signal = Signal

	function Utility.Create(instanceType)
		return function(data)
			local obj = Instance.new(instanceType)
			for k, v in pairs(data) do
				if type(k) == 'number' then
					v.Parent = obj
				else
					obj[k] = v
				end
			end
			return obj
		end
	end

	local function ViewSizeX()
		local camera = workspace.CurrentCamera
		local x = camera and camera.ViewportSize.X or 0
		local y = camera and camera.ViewportSize.Y or 0
		if x == 0 then
			return 1024
		else
			if x > y then
				return x
			else
				return y
			end
		end
	end
	Utility.ViewSizeX = ViewSizeX

	local function ViewSizeY()
		local camera = workspace.CurrentCamera
		local x = camera and camera.ViewportSize.X or 0
		local y = camera and camera.ViewportSize.Y or 0
		if y == 0 then
			return 768
		else
			if x > y then
				return y
			else
				return x
			end
		end
	end
	Utility.ViewSizeY = ViewSizeY

	local function AspectRatio()
		return ViewSizeX() / ViewSizeY()
	end
	Utility.AspectRatio = AspectRatio

	local function FindChacterAncestor(part)
		if part then
			local humanoid = part:FindFirstChild("Humanoid")
			if humanoid then
				return part, humanoid
			else
				return FindChacterAncestor(part.Parent)
			end
		end
	end
	Utility.FindChacterAncestor = FindChacterAncestor


	local function GetUnitRay(x, y, viewWidth, viewHeight, camera)
		return camera:ScreenPointToRay(x, y)
	end
	Utility.GetUnitRay = GetUnitRay

	local function Raycast(ray, ignoreNonCollidable, ignoreList)
		local ignoreList = ignoreList or {}
		local hitPart, hitPos = RayCastIgnoreList(workspace, ray, ignoreList)
		if hitPart then
			if ignoreNonCollidable and hitPart.CanCollide == false then
				table.insert(ignoreList, hitPart)
				return Raycast(ray, ignoreNonCollidable, ignoreList)
			end
			return hitPart, hitPos
		end
		return nil, nil
	end
	Utility.Raycast = Raycast


	Utility.Round = function(num, roundToNearest)
		roundToNearest = roundToNearest or 1
		return math_floor((num + roundToNearest/2) / roundToNearest) * roundToNearest
	end

	local function AveragePoints(positions)
		local avgPos = ZERO_VECTOR2
		if #positions > 0 then
			for i = 1, #positions do
				avgPos = avgPos + positions[i]
			end
			avgPos = avgPos / #positions
		end
		return avgPos
	end
	Utility.AveragePoints = AveragePoints

	local function FuzzyEquals(numa, numb)
		return numa + 0.1 > numb and numa - 0.1 < numb
	end
	Utility.FuzzyEquals = FuzzyEquals

	local LastInput = 0
	UIS.InputBegan:connect(function(inputObject, wasSunk)
		if not wasSunk then
			if inputObject.UserInputType == Enum.UserInputType.Touch or
					inputObject.UserInputType == Enum.UserInputType.MouseButton1 or
					inputObject.UserInputType == Enum.UserInputType.MouseButton2 then
				LastInput = tick()
			end
		end
	end)
	Utility.GetLastInput = function()
		return LastInput
	end
end

local humanoidCache = {}
local function findPlayerHumanoid(player)
	local character = player and player.Character
	if character then
		local resultHumanoid = humanoidCache[player]
		if resultHumanoid and resultHumanoid.Parent == character then
			return resultHumanoid
		else
			humanoidCache[player] = nil -- Bust Old Cache
			local humanoid = character:FindFirstChildOfClass("Humanoid")
			if humanoid then
				humanoidCache[player] = humanoid
			end
			return humanoid
		end
	end
end

local GetThetaBetweenCFrames; do
	local components = CFrame.new().GetComponents
	local inverse = CFrame.new().Inverse
	local acos = math.acos

	GetThetaBetweenCFrames = function(c0, c1) -- (CFrame from, CFrame to) -> (float theta)
		local _, _, _, xx, yx, zx,
		               xy, yy, zy,
		               xz, yz, zz = components(inverse(c0)*c1)
		local cosTheta = (xx + yy + zz - 1)/2

		if cosTheta >= 0.999 then
			-- Same rotation
			return 0
		elseif cosTheta <= -0.999 then
			-- Oposite rotations
			return math_pi
		else
			return acos(cosTheta)
		end
	end
end

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

local Signal = Utility.Signal
local Create = Utility.Create

--------------------------CHARACTER CONTROL-------------------------------
local function CreateController()
	local this = {}

	this.TorsoLookPoint = nil

	function this:SetTorsoLookPoint(point)
		local humanoid = findPlayerHumanoid(Player)
		if humanoid then
			humanoid.AutoRotate = false
		end
		this.TorsoLookPoint = point
		self:UpdateTorso()
		delay(2,
			function()
			-- this isnt technically correct for detecting if this is the last issue to the setTorso function
			if this.TorsoLookPoint == point then
				this.TorsoLookPoint = nil
				if humanoid then
					humanoid.AutoRotate = true
				end
			end
		end)
	end

	function this:UpdateTorso(point)
		if this.TorsoLookPoint then
			point = this.TorsoLookPoint
		else
			return
		end

		local humanoid = findPlayerHumanoid(Player)
		local torso = humanoid and humanoid.Torso
		if torso then
			local lookVec = (point - torso.CFrame.p).unit
			local squashedLookVec = Vector3_new(lookVec.X, 0, lookVec.Z).unit
			torso.CFrame = CFrame.new(torso.CFrame.p, torso.CFrame.p + squashedLookVec)
		end
	end

	return this
end

local CharacterControl = CreateController()
-----------------------------------------------------------------------

--------------------------PC AUTO JUMPER-------------------------------

local function GetCharacter()
	return Player and Player.Character
end

local function GetTorso()
	local humanoid = findPlayerHumanoid(Player)
	return humanoid and humanoid.Torso
end

local function IsPartAHumanoid(part)
	return part and part.Parent and (part.Parent:FindFirstChild('Humanoid') ~= nil)
end

local function doAutoJump()
	local character = GetCharacter()
	if (character == nil) then
		return;
	end

	local humanoid = findPlayerHumanoid(Player)
	if (humanoid == nil) then
		return;
	end

	local rayLength = 1.5;
	-- This is how high a ROBLOXian jumps from the mid point of his torso
	local jumpHeight = 7.0;

	local torso = GetTorso()
	if (torso == nil) then
		return;
	end

	local torsoCFrame = torso.CFrame;
	local torsoLookVector = torsoCFrame.lookVector;
	local torsoPos = torsoCFrame.p;

	local torsoRay = Ray.new(torsoPos + Vector3_new(0, -torso.Size.Y/2, 0), torsoLookVector * rayLength);
	local jumpRay = Ray.new(torsoPos + Vector3_new(0, jumpHeight - torso.Size.Y, 0), torsoLookVector * rayLength);

	local hitPart, _ = RayCastIgnoreList(workspace, torsoRay, {character}, false)
	local jumpHitPart, _ = RayCastIgnoreList(workspace, jumpRay, {character}, false)

	if (hitPart and jumpHitPart == nil and hitPart.CanCollide == true) then
		 -- NOTE: this follow line is not in the C++ impl, but an improvement  in Click to Move
		if not IsPartAHumanoid(hitPart) then
			humanoid.Jump = true;
		end
	end
end

local NO_JUMP_STATES =
{
	[Enum.HumanoidStateType.FallingDown] = false;
	[Enum.HumanoidStateType.Flying] = false;
	[Enum.HumanoidStateType.Freefall] = false;
	[Enum.HumanoidStateType.GettingUp] = false;
	[Enum.HumanoidStateType.Ragdoll] = false;
	[Enum.HumanoidStateType.Running] = false;
	[Enum.HumanoidStateType.Seated] = false;
	[Enum.HumanoidStateType.Swimming] = false;

	-- Special case to detect if we are on a ladder
	[Enum.HumanoidStateType.Climbing] = false;
}

local function enableAutoJump()
	local humanoid = findPlayerHumanoid(Player)
	local currentState = humanoid and humanoid:GetState()
	if currentState then
		return NO_JUMP_STATES[currentState] == nil
	end
	return false
end

local function getAutoJump()
	return true
end

local function vec3IsZero(vec3)
	return vec3.magnitude < 0.05
end

-- NOTE: This function is radically different from the engine's implementation
local walkVelocityVector = Vector3_new(1,1,1)
local function calcDesiredWalkVelocity()
	-- TEMP
	return walkVelocityVector
end

local function preStepSimulatorSide(dt)
	if getAutoJump() and enableAutoJump() then
		local desiredWalkVelocity = calcDesiredWalkVelocity();
		if (not vec3IsZero(desiredWalkVelocity)) then
			doAutoJump();
		end
	end
end

local function AutoJumper()
	local this = {}
	local running = false
	local runRoutine = nil

	function this:Run()
		running = true
		local thisRoutine = nil
		thisRoutine = coroutine.create(function()
			while running and thisRoutine == runRoutine do
				this:Step()
				wait()
			end
		end)
		runRoutine = thisRoutine
		coroutine.resume(thisRoutine)
	end

	function this:Stop()
		running = false
	end

	function this:Step()
		preStepSimulatorSide()
	end

	return this
end

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

-----------------------------------PATHER--------------------------------------

local function CreateDestinationIndicator(pos)
	local destinationGlobe = Create'Part'
	{
		Name = 'PathGlobe';
		TopSurface = 'Smooth';
		BottomSurface = 'Smooth';
		Shape = 'Ball';
		CanCollide = false;
		Size = Vector3_new(2,2,2);
		BrickColor = BrickColor.new('Institutional white');
		Transparency = 0;
		Anchored = true;
		CFrame = CFrame.new(pos);
	}
	return destinationGlobe
end

local function Pather(character, point)
	local this = {}

	this.Cancelled = false
	this.Started = false

	this.Finished = Signal.Create()
	this.PathFailed = Signal.Create()
	this.PathStarted = Signal.Create()

	this.PathComputed = false

	function this:YieldUntilPointReached(character, point, timeout)
		timeout = timeout or 10000000

		local humanoid = findPlayerHumanoid(Player)
		local torso = humanoid and humanoid.Torso
		local start = tick()
		local lastMoveTo = start
		while torso and tick() - start < timeout and this.Cancelled == false do
			local diffVector = (point - torso.CFrame.p)
			local xzMagnitude = (diffVector * XZ_VECTOR3).magnitude
			if xzMagnitude < 6 then
				-- Jump if the path is telling is to go upwards
				if diffVector.Y >= 2.2 then
					humanoid.Jump = true
				end
			end
			-- The hard-coded number 2 here is from the engine's MoveTo implementation
			if xzMagnitude < 2 then
				return true
			end
			-- Keep on issuing the move command because it will automatically quit every so often.
			if tick() - lastMoveTo > 1.5 then
				humanoid:MoveTo(point)
				lastMoveTo = tick()
			end
			CharacterControl:UpdateTorso(point)
			wait()
		end
		return false
	end

	function this:Cancel()
		this.Cancelled = true
		local humanoid = findPlayerHumanoid(Player)
		local torso = humanoid and humanoid.Torso
		if humanoid and torso then
			humanoid:MoveTo(torso.CFrame.p)
		end
	end

	function this:CheckOcclusion(point1, point2, character, torsoRadius)
		local humanoid = findPlayerHumanoid(Player)
		local torso = humanoid and humanoid.Torso
		if torsoRadius == nil then
			torsoRadius = torso and Vector3_new(torso.Size.X/2,0,torso.Size.Z/2) or XZ_VECTOR3
		end

		local diffVector = point2 - point1
		local directionVector = diffVector.unit

		local rightVector = Y_VECTOR3:Cross(directionVector) * torsoRadius

		local rightPart, _ = Utility.Raycast(Ray.new(point1 + rightVector, diffVector + rightVector), true, {character})
		local hitPart, _ = Utility.Raycast(Ray.new(point1, diffVector), true, {character})
		local leftPart, _ = Utility.Raycast(Ray.new(point1 - rightVector, diffVector - rightVector), true, {character})

		if rightPart or hitPart or leftPart then
			return false
		end

		-- Make sure we have somewhere to stand on
		local midPt = (point2 + point1) / 2
		local studsBetweenSamples = 2
		for i = 1, math_floor(diffVector.magnitude/studsBetweenSamples) do
			local downPart, _ = Utility.Raycast(Ray.new(point1 + directionVector * i * studsBetweenSamples, Vector3_new(0,-7,0)), true, {character})
			if not downPart then
				return false
			end
		end

		return true
	end

	function this:SmoothPoints(pathToSmooth)
		local result = {}

		local humanoid = findPlayerHumanoid(Player)
		local torso = humanoid and humanoid.Torso
		for i = 1, #pathToSmooth do
			table.insert(result, pathToSmooth[i])
		end

		-- Backwards for safe-deletion
		for i = #result - 1, 1, -1 do
			if i + 1 <= #result then

				local nextPoint = result[i+1]
				local thisPoint = result[i]

				local lastPoint = result[i-1]
				if lastPoint == nil then
					lastPoint = torso and Vector3_new(torso.CFrame.p.X, thisPoint.Y, torso.CFrame.p.Z)
				end

				if lastPoint and Utility.FuzzyEquals(thisPoint.Y, lastPoint.Y) and Utility.FuzzyEquals(thisPoint.Y, nextPoint.Y) then
					if this:CheckOcclusion(lastPoint, nextPoint, character) then
						table.remove(result, i)
						-- Move i back one to recursively-smooth
						i = i + 1
					end
				end
			end
		end

		return result
	end

	function this:CheckNeighboringCells(character)
		local pathablePoints = {}
		local humanoid = findPlayerHumanoid(Player)
		local torso = character and humanoid and humanoid.Torso
		if torso then
			local torsoCFrame = torso.CFrame
			local torsoPos = torsoCFrame.p
			-- Minus and plus 2 is so we can get it into the cell-corner space and then translate it back into cell-center space
			local roundedPos = Vector3_new(Utility.Round(torsoPos.X-2,4)+2, Utility.Round(torsoPos.Y-2,4)+2, Utility.Round(torsoPos.Z-2,4)+2)
			local neighboringCells = {}
			for x = -4, 4, 8 do
				for z = -4, 4, 8 do
					table.insert(neighboringCells, roundedPos + Vector3_new(x,0,z))
				end
			end
			for _, testPoint in pairs(neighboringCells) do
				local pathable = this:CheckOcclusion(roundedPos, testPoint, character, ZERO_VECTOR3)
				if pathable then
					table.insert(pathablePoints, testPoint)
				end
			end
		end
		return pathablePoints
	end

	function this:ComputeDirectPath()
		local humanoid = findPlayerHumanoid(Player)
		local torso = humanoid and humanoid.Torso
		if torso then
			local startPt = torso.CFrame.p
			local finishPt = point
			if (finishPt - startPt).magnitude < 150 then
				-- move back the destination by 2 studs or otherwise the pather will collide with the object we are trying to reach
				finishPt = finishPt - (finishPt - startPt).unit * 2
				if this:CheckOcclusion(startPt, finishPt, character, ZERO_VECTOR3) then
					local pathResult = {}
					pathResult.Status = Enum.PathStatus.Success
					function pathResult:GetPointCoordinates()
						return {finishPt}
					end
					return pathResult
				end
			end
		end
	end

	local function AllAxisInThreshhold(targetPt, otherPt, threshold)
		return math_abs(targetPt.X - otherPt.X) <= threshold and
			math_abs(targetPt.Y - otherPt.Y) <= threshold and
			math_abs(targetPt.Z - otherPt.Z) <= threshold
	end

	function this:ComputePath()
		local smoothed = false
		local humanoid = findPlayerHumanoid(Player)
		local torso = humanoid and humanoid.Torso
		if torso then
			if this.PathComputed then return end
			this.PathComputed = true
			-- Will yield the script since it is an Async script (start, finish, maxDistance)
			-- Try to use the smooth function, but it may not exist yet :(
			local success = pcall(function()
				-- 3 is height from torso cframe to ground
				this.pathResult = PathfindingService:ComputeSmoothPathAsync(torso.CFrame.p - Vector3_new(0,3,0), point, 400)
				smoothed = true
			end)
			if not success then
				-- 3 is height from torso cframe to ground
				this.pathResult = PathfindingService:ComputeRawPathAsync(torso.CFrame.p - Vector3_new(0,3,0), point, 400)
				smoothed = false
			end
			this.pointList = this.pathResult and this.pathResult:GetPointCoordinates()
			local pathFound = false
			if this.pathResult.Status == Enum.PathStatus.FailFinishNotEmpty then
				-- Lets try again with a slightly set back start point; it is ok to do this again so the FailFinishNotEmpty uses little computation
				local diffVector = point - (workspace.CurrentCamera :: Camera).CoordinateFrame.p
				if diffVector.magnitude > 2 then
					local setBackPoint = point - (diffVector).unit * 2.1
					local success = pcall(function()
						this.pathResult = PathfindingService:ComputeSmoothPathAsync(torso.CFrame.p, setBackPoint, 400)
						smoothed = true
					end)
					if not success then
						this.pathResult = PathfindingService:ComputeRawPathAsync(torso.CFrame.p, setBackPoint, 400)
						smoothed = false
					end
					this.pointList = this.pathResult and this.pathResult:GetPointCoordinates()
					pathFound = true
				end
			end
			if this.pathResult.Status == Enum.PathStatus.ClosestNoPath and #this.pointList >= 1 and pathFound == false then
				local otherPt = this.pointList[#this.pointList]
				if AllAxisInThreshhold(point, otherPt, 4) and (torso.CFrame.p - point).magnitude > (otherPt - point).magnitude then
					local pathResult = {}
					pathResult.Status = Enum.PathStatus.Success
					function pathResult:GetPointCoordinates()
						return {this.pointList}
					end
					this.pathResult = pathResult
					pathFound = true
				end
			end
			if (this.pathResult.Status == Enum.PathStatus.FailStartNotEmpty or this.pathResult.Status == Enum.PathStatus.ClosestNoPath) and pathFound == false then
				local pathablePoints = this:CheckNeighboringCells(character)
				for _, otherStart in pairs(pathablePoints) do
					local pathResult;
					local success = pcall(function()
						pathResult = PathfindingService:ComputeSmoothPathAsync(otherStart, point, 400)
						smoothed = true
					end)
					if not success then
						pathResult = PathfindingService:ComputeRawPathAsync(otherStart, point, 400)
						smoothed = false
					end
					if pathResult and pathResult.Status == Enum.PathStatus.Success then
						this.pathResult = pathResult
						if this.pathResult then
							this.pointList = this.pathResult:GetPointCoordinates()
							table.insert(this.pointList, 1, otherStart)
						end
						break
					end
				end
			end
			if DirectPathEnabled then
				if this.pathResult.Status ~= Enum.PathStatus.Success then
					local directPathResult = this:ComputeDirectPath()
					if directPathResult and directPathResult.Status == Enum.PathStatus.Success then
						this.pathResult = directPathResult
						this.pointList = directPathResult:GetPointCoordinates()
					end
				end
			end
		end
		return smoothed
	end

	function this:IsValidPath()
		this:ComputePath()
		local pathStatus = this.pathResult.Status
		return pathStatus == Enum.PathStatus.Success
	end

	function this:GetPathStatus()
		this:ComputePath()
		return this.pathResult.Status
	end

	function this:Start()
		if CurrentSeatPart then
			return
		end
		spawn(function()
			local humanoid = findPlayerHumanoid(Player)
			--humanoid.AutoRotate = false
			local torso = humanoid and humanoid.Torso
			if torso then
				if this.Started then return end
				this.Started = true
				-- Will yield the script since it is an Async function script (start, finish, maxDistance)
				local smoothed = this:ComputePath()
				if this:IsValidPath() then
					this.PathStarted:fire()
					-- smooth out zig-zaggy paths
					local smoothPath = smoothed and this.pointList or this:SmoothPoints(this.pointList)
					for i, point in pairs(smoothPath) do
						if humanoid then
							if this.Cancelled then
								return
							end

							local wayPoint = nil
							if SHOW_PATH then
								wayPoint = CreateDestinationIndicator(point)
								wayPoint.BrickColor = BrickColor.new("New Yeller")
								wayPoint.Parent = workspace
								print(wayPoint.CFrame.p)
							end

							humanoid:MoveTo(point)

							local distance = ((torso.CFrame.p - point) * XZ_VECTOR3).magnitude
							local approxTime = 10
							if math_abs(humanoid.WalkSpeed) > 0 then
								approxTime = distance / math_abs(humanoid.WalkSpeed)
							end

							local yielding = true

							if i == 1 then
								--local rotatedCFrame = CameraModule:LookAtPreserveHeight(point)
								if CameraModule then
									local rotatedCFrame = CameraModule:LookAtPreserveHeight(smoothPath[#smoothPath])
									local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame)
								end
								--CharacterControl:SetTorsoLookPoint(point)
							end
							---[[
							if (humanoid.Torso.CFrame.p - point).magnitude > 9 then
								spawn(function()
									while yielding and this.Cancelled == false do
										if CameraModule then
											local look = CameraModule:GetCameraLook()
											local squashedLook = (look * XZ_VECTOR3).unit
											local direction = ((point - CameraModule.cframe.p) * XZ_VECTOR3).unit

											local theta = math_deg(math_acos(squashedLook:Dot(direction)))

											if tick() - Utility.GetLastInput() > 2 and theta > ((workspace.CurrentCamera :: Camera).FieldOfView / 2) then
												local rotatedCFrame = CameraModule:LookAtPreserveHeight(point)
												local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame)
												--return
											end
										end
										wait(0.1)
									end
								end)
							end
							--]]
							local didReach = this:YieldUntilPointReached(character, point, approxTime * 3 + 1)

							yielding = false

							if SHOW_PATH then
								wayPoint:Destroy()
							end

							if not didReach then
								this.PathFailed:fire()
								return
							end
						end
					end

					this.Finished:fire()
					return
				end
			end
			this.PathFailed:fire()
		end)
	end

	return this
end

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

local function FlashRed(object)
	local origColor = object.BrickColor
	local redColor = BrickColor.new("Really red")
	local start = tick()
	local duration = 4
	spawn(function()
		while object and tick() - start < duration do
			object.BrickColor = origColor
			wait(0.13)
			if object then
				object.BrickColor = redColor
			end
			wait(0.13)
		end
	end)
end

--local joystickWidth = 250
--local joystickHeight = 250
local function IsInBottomLeft(pt)
	local joystickHeight = math.min(Utility.ViewSizeY() * 0.33, 250)
	local joystickWidth = joystickHeight
	return pt.X <= joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
end

local function IsInBottomRight(pt)
	local joystickHeight = math.min(Utility.ViewSizeY() * 0.33, 250)
	local joystickWidth = joystickHeight
	return pt.X >= Utility.ViewSizeX() - joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
end

local function CheckAlive(character)
	local humanoid = findPlayerHumanoid(Player)
	return humanoid ~= nil and humanoid.Health > 0
end

local function GetEquippedTool(character)
	if character ~= nil then
		for _, child in pairs(character:GetChildren()) do
			if child:IsA('Tool') then
				return child
			end
		end
	end
end

local function ExploreWithRayCast(currentPoint, originDirection)
	local TestDistance = 40
	local TestVectors = {}
	do
		local forwardVector = originDirection;
		for i = 0, 15 do
			table.insert(TestVectors, CFrame.Angles(0, math.pi / 8 * i, 0) * forwardVector)
		end
	end

	local testResults = {}
	-- Heuristic should be something along the lines of distance and closeness to the traveling direction
	local function ExploreHeuristic()
		for _, testData in pairs(testResults) do
			local walkDirection = -1 * originDirection
			local directionCoeff = (walkDirection:Dot(testData['Vector']) + 1) / 2
			local distanceCoeff = testData['Distance'] / TestDistance
			testData["Value"] = directionCoeff * distanceCoeff
		end
	end

	for i, vec in pairs(TestVectors) do
		local hitPart, hitPos = Utility.Raycast(Ray.new(currentPoint, vec * TestDistance), true, {Player.Character})
		if hitPos then
			table.insert(testResults, {Vector = vec; Distance = (hitPos - currentPoint).magnitude})
		else
			table.insert(testResults, {Vector = vec; Distance = TestDistance})
		end
	end

	ExploreHeuristic()

	table.sort(testResults, function(a,b) return a["Value"] > b["Value"] end)

	return testResults
end

local TapId = 1
local ExistingPather = nil
local ExistingIndicator = nil
local PathCompleteListener = nil
local PathFailedListener = nil

local function CleanupPath()
	DrivingTo = nil
	if ExistingPather then
		ExistingPather:Cancel()
	end
	if PathCompleteListener then
		PathCompleteListener:disconnect()
		PathCompleteListener = nil
	end
	if PathFailedListener then
		PathFailedListener:disconnect()
		PathFailedListener = nil
	end
	if ExistingIndicator then
		DebrisService:AddItem(ExistingIndicator, 0)
		ExistingIndicator = nil
	end
end

local function getExtentsSize(Parts)
	local maxX = Parts[1].Position.X
	local maxY = Parts[1].Position.Y
	local maxZ = Parts[1].Position.Z
	local minX = Parts[1].Position.X
	local minY = Parts[1].Position.Y
	local minZ = Parts[1].Position.Z
	for i = 2, #Parts do
		maxX = math_max(maxX, Parts[i].Position.X)
		maxY = math_max(maxY, Parts[i].Position.Y)
		maxZ = math_max(maxZ, Parts[i].Position.Z)
		minX = math_min(minX, Parts[i].Position.X)
		minY = math_min(minY, Parts[i].Position.Y)
		minZ = math_min(minZ, Parts[i].Position.Z)
	end
	return Region3.new(Vector3_new(minX, minY, minZ), Vector3_new(maxX, maxY, maxZ))
end

local function inExtents(Extents, Position)
	if Position.X < (Extents.CFrame.p.X - Extents.Size.X/2) or Position.X > (Extents.CFrame.p.X + Extents.Size.X/2) then
		return false
	end
	if Position.Z < (Extents.CFrame.p.Z - Extents.Size.Z/2) or Position.Z > (Extents.CFrame.p.Z + Extents.Size.Z/2) then
		return false
	end
	--ignoring Y for now
	return true
end

local AutoJumperInstance = nil
local ShootCount = 0
local FailCount = 0
local function OnTap(tapPositions, goToPoint)
	-- Good to remember if this is the latest tap event
	TapId = TapId + 1
	local thisTapId = TapId


	local camera = workspace.CurrentCamera
	local character = Player.Character


	if not CheckAlive(character) then return end

	-- This is a path tap position
	if #tapPositions == 1 or goToPoint then
		if camera then
			local unitRay = Utility.GetUnitRay(tapPositions[1].x, tapPositions[1].y, MyMouse.ViewSizeX, MyMouse.ViewSizeY, camera)
			local ray = Ray.new(unitRay.Origin, unitRay.Direction*400)
			local hitPart, hitPt = Utility.Raycast(ray, true, {character})

			local hitChar, hitHumanoid = Utility.FindChacterAncestor(hitPart)
			local torso = character and character:FindFirstChild("Humanoid") and character:FindFirstChild("Humanoid").Torso
			local startPos = torso.CFrame.p
			if goToPoint then
				hitPt = goToPoint
				hitChar = nil
			end
			if hitChar and hitHumanoid and hitHumanoid.Torso and (hitHumanoid.Torso.CFrame.p - torso.CFrame.p).magnitude < 7 then
				CleanupPath()

				local myHumanoid = findPlayerHumanoid(Player)
				if myHumanoid then
					myHumanoid:MoveTo(hitPt)
				end

				ShootCount = ShootCount + 1
				local thisShoot = ShootCount
				-- Do shooot
				local currentWeapon = GetEquippedTool(character)
				if currentWeapon then
					currentWeapon:Activate()
				end
			elseif hitPt and character and not CurrentSeatPart then
				local thisPather = Pather(character, hitPt)
				if thisPather:IsValidPath() then
					FailCount = 0
					-- TODO: Remove when bug in engine is fixed
					Player:Move(Vector3_new(1, 0, 0))
					Player:Move(ZERO_VECTOR3)
					thisPather:Start()
					if BindableEvent_OnFailStateChanged then
						BindableEvent_OnFailStateChanged:Fire(false)
					end
					CleanupPath()

					local destinationGlobe = CreateDestinationIndicator(hitPt)
					destinationGlobe.Parent = camera

					ExistingPather = thisPather
					ExistingIndicator = destinationGlobe

					if AutoJumperInstance then
						AutoJumperInstance:Run()
					end

					PathCompleteListener = thisPather.Finished:connect(function()
						if AutoJumperInstance then
							AutoJumperInstance:Stop()
						end
						if destinationGlobe then
							if ExistingIndicator == destinationGlobe then
								ExistingIndicator = nil
							end
							DebrisService:AddItem(destinationGlobe, 0)
							destinationGlobe = nil
						end
						if hitChar then
							local humanoid = findPlayerHumanoid(Player)
							ShootCount = ShootCount + 1
							local thisShoot = ShootCount
							-- Do shoot
							local currentWeapon = GetEquippedTool(character)
							if currentWeapon then
								currentWeapon:Activate()
							end
							if humanoid then
								humanoid:MoveTo(hitPt)
							end
						end
						local finishPos = torso and torso.CFrame.p --hitPt
						if finishPos and startPos and tick() - Utility.GetLastInput() > 2 then
							local exploreResults = ExploreWithRayCast(finishPos, ((startPos - finishPos) * XZ_VECTOR3).unit)
							-- Check for Nans etc..
							if exploreResults[1] and exploreResults[1]["Vector"] and exploreResults[1]["Vector"].magnitude >= 0.5 and exploreResults[1]["Distance"] > 3 then
								if CameraModule then
									local rotatedCFrame = CameraModule:LookAtPreserveHeight(finishPos + exploreResults[1]["Vector"] * exploreResults[1]["Distance"])
									local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame)
								end
							end
						end
					end)
					PathFailedListener = thisPather.PathFailed:connect(function()
						if AutoJumperInstance then
							AutoJumperInstance:Stop()
						end
						if destinationGlobe then
							FlashRed(destinationGlobe)
							DebrisService:AddItem(destinationGlobe, 3)
						end
					end)
				else
					if hitPt then
						-- Feedback here for when we don't have a good path
						local failedGlobe = CreateDestinationIndicator(hitPt)
						FlashRed(failedGlobe)
						DebrisService:AddItem(failedGlobe, 1)
						failedGlobe.Parent = camera
						if ExistingIndicator == nil then
							FailCount = FailCount + 1
							if FailCount >= 3 then
								if BindableEvent_OnFailStateChanged then
									BindableEvent_OnFailStateChanged:Fire(true)
								end
								CleanupPath()
							end
						end
					end
				end
			elseif hitPt and character and CurrentSeatPart then
				local destinationGlobe = CreateDestinationIndicator(hitPt)
				destinationGlobe.Parent = camera
				ExistingIndicator = destinationGlobe
				DrivingTo = hitPt
				local ConnectedParts = CurrentSeatPart:GetConnectedParts(true)

				while wait() do
					if CurrentSeatPart and ExistingIndicator == destinationGlobe then
						local ExtentsSize = getExtentsSize(ConnectedParts)
						if inExtents(ExtentsSize, destinationGlobe.Position) then
							DebrisService:AddItem(destinationGlobe, 0)
							destinationGlobe = nil
							DrivingTo = nil
							break
						end
					else
						DebrisService:AddItem(destinationGlobe, 0)
						if CurrentSeatPart == nil and destinationGlobe == ExistingIndicator then
							DrivingTo = nil
							OnTap(tapPositions, hitPt)
						end
						destinationGlobe = nil
						break
					end
				end

			else
				-- no hit pt
			end
		end
	elseif #tapPositions >= 2 then
		if camera then
			ShootCount = ShootCount + 1
			local thisShoot = ShootCount
			-- Do shoot
			local avgPoint = Utility.AveragePoints(tapPositions)
			local unitRay = Utility.GetUnitRay(avgPoint.x, avgPoint.y, MyMouse.ViewSizeX, MyMouse.ViewSizeY, camera)
			local currentWeapon = GetEquippedTool(character)
			if currentWeapon then
				currentWeapon:Activate()
			end
		end
	end
end


local function CreateClickToMoveModule()
	local this = {}

	local LastStateChange = 0
	local LastState = Enum.HumanoidStateType.Running
	local FingerTouches = {}
	local NumUnsunkTouches = 0
	-- PC simulation
	local mouse1Down = tick()
	local mouse1DownPos = Vector2.new()
	local mouse2Down = tick()
	local mouse2DownPos = Vector2.new()
	local mouse2Up = tick()

	local movementKeys = {
		[Enum.KeyCode.W] = true;
		[Enum.KeyCode.A] = true;
		[Enum.KeyCode.S] = true;
		[Enum.KeyCode.D] = true;
		[Enum.KeyCode.Up] = true;
		[Enum.KeyCode.Down] = true;
	}

	local TapConn = nil
	local InputBeganConn = nil
	local InputChangedConn = nil
	local InputEndedConn = nil
	local HumanoidDiedConn = nil
	local CharacterChildAddedConn = nil
	local OnCharacterAddedConn = nil
	local CharacterChildRemovedConn = nil
	local RenderSteppedConn = nil
	local HumanoidSeatedConn = nil

	local function disconnectEvent(event)
		if event then
			event:disconnect()
		end
	end

	local function DisconnectEvents()
		disconnectEvent(TapConn)
		disconnectEvent(InputBeganConn)
		disconnectEvent(InputChangedConn)
		disconnectEvent(InputEndedConn)
		disconnectEvent(HumanoidDiedConn)
		disconnectEvent(CharacterChildAddedConn)
		disconnectEvent(OnCharacterAddedConn)
		disconnectEvent(RenderSteppedConn)
		disconnectEvent(CharacterChildRemovedConn)
		pcall(function() RunService:UnbindFromRenderStep("ClickToMoveRenderUpdate") end)
		disconnectEvent(HumanoidSeatedConn)
	end



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

	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

	-- Setup the camera
	CameraModule = ClassicCameraModule()

	do
		-- Extend The Camera Module Class
		function CameraModule:LookAtPreserveHeight(newLookAtPt)
			local camera = 	workspace.CurrentCamera

			local focus = camera.Focus.p

			local cameraCFrame = CameraModule.cframe
			local mag = Vector3_new(cameraCFrame.lookVector.x, 0, cameraCFrame.lookVector.z).magnitude
			local newLook = (Vector3_new(newLookAtPt.x, focus.y, newLookAtPt.z) - focus).unit * mag
			local flippedLook = newLook + Vector3_new(0, cameraCFrame.lookVector.y, 0)

			local distance = (focus - cameraCFrame.p).magnitude

			local newCamPos = focus - flippedLook.unit * distance
			return CFrame.new(newCamPos, newCamPos + flippedLook)
		end

		local lerp = CFrame.new().Lerp
		function CameraModule:TweenCameraLook(desiredCFrame, speed)
			local e = 2.718281828459

			local function SCurve(t)
				return 1/(1 + e^(-t*1.5))
			end

			local function easeOutSine(t, b, c, d)
				if t >= d then return b + c end
				return c * math_sin(t/d * (math_pi/2)) + b;
			end

			local c0, c1 = CFrame_new(ZERO_VECTOR3, self:GetCameraLook()), desiredCFrame - desiredCFrame.p
			local theta = GetThetaBetweenCFrames(c0, c1)
			theta = clamp(0, math_pi, theta)
			local duration = 0.65 * SCurve(theta - math_pi/4) + 0.15
			if speed then
				duration = theta / speed
			end
			local start = tick()
			local finish = start + duration

			self.UpdateTweenFunction = function()
				local currTime = tick() - start
				local alpha = clamp(0, 1, easeOutSine(currTime, 0, 1, duration))
				local newCFrame = lerp(c0, c1, alpha)
				local y = findAngleBetweenXZVectors(newCFrame.lookVector, self:GetCameraLook())
				if IsFinite(y) and math_abs(y) > 0.0001 then
					self.RotateInput = self.RotateInput + Vector2_new(y, 0)
				end
				return (currTime >= finish or alpha >= 1)
			end
		end
	end
	--- Done Extending


	local function OnTouchBegan(input, processed)
		if FingerTouches[input] == nil and not processed then
			NumUnsunkTouches = NumUnsunkTouches + 1
		end
		FingerTouches[input] = processed
	end

	local function OnTouchChanged(input, processed)
		if FingerTouches[input] == nil then
			FingerTouches[input] = processed
			if not processed then
				NumUnsunkTouches = NumUnsunkTouches + 1
			end
		end
	end

	local function OnTouchEnded(input, processed)
			--print("Touch tap fake:" , processed)
			--if not processed then
			--	OnTap({input.Position})
			--end
		if FingerTouches[input] ~= nil and FingerTouches[input] == false then
			NumUnsunkTouches = NumUnsunkTouches - 1
		end
		FingerTouches[input] = nil
	end


	local function OnCharacterAdded(character)
		DisconnectEvents()

		InputBeganConn = UIS.InputBegan:connect(function(input, processed)
			if input.UserInputType == Enum.UserInputType.Touch then
				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(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
				mouse1Down = tick()
				mouse1DownPos = input.Position
			end
			if input.UserInputType == Enum.UserInputType.MouseButton2 then
				mouse2Down = tick()
				mouse2DownPos = input.Position
			end
		end)

		InputChangedConn = UIS.InputChanged:connect(function(input, processed)
			if input.UserInputType == Enum.UserInputType.Touch then
				OnTouchChanged(input, processed)
			end
		end)

		InputEndedConn = UIS.InputEnded:connect(function(input, processed)
			if input.UserInputType == Enum.UserInputType.Touch then
				OnTouchEnded(input, processed)
			end

			if input.UserInputType == Enum.UserInputType.MouseButton2 then
				mouse2Up = tick()
				local currPos = input.Position
				if mouse2Up - mouse2Down < 0.25 and (currPos - mouse2DownPos).magnitude < 5 then
					local positions = {currPos}
					OnTap(positions)
				end
			end
		end)

		TapConn = UIS.TouchTap:connect(function(touchPositions, processed)
			if not processed then
				OnTap(touchPositions)
			end
		end)

		if not UIS.TouchEnabled then -- PC
			if AutoJumperInstance then
				AutoJumperInstance:Stop()
				AutoJumperInstance = nil
			end
			AutoJumperInstance = AutoJumper()
		end

		local function getThrottleAndSteer(object, point)
			local lookVector = (point - object.Position)
			lookVector = Vector3_new(lookVector.X, 0, lookVector.Z).unit
			local objectVector = Vector3_new(object.CFrame.lookVector.X, 0, object.CFrame.lookVector.Z).unit
			local dirVector = lookVector - objectVector
			local mag = dirVector.magnitude
			local degrees = math_deg(math_acos(lookVector:Dot(objectVector)))
			local side = (object.CFrame:pointToObjectSpace(point).X > 0)
			local throttle = 0
			if mag < 0.25 then
				throttle = 1
			end
			if mag > 1.8 then
				throttle = -1
			end
			local distance = CurrentSeatPart.Position - DrivingTo
			local velocity = CurrentSeatPart.Velocity
			if velocity.magnitude*1.5 > distance.magnitude then
				if velocity.magnitude*0.5 > distance.magnitude then
					throttle = -throttle
				else
					throttle = 0
				end
			end
			local steer = 0
			if degrees > 5 and degrees < 175 then
				if side then
					steer = 1
				else
					steer = -1
				end
			end
			local rotatingAt = math_deg(CurrentSeatPart.RotVelocity.magnitude)
			local degreesAway = math_max(math_min(degrees, 180 - degrees), 10)
			if (CurrentSeatPart.RotVelocity.X < 0)== (steer < 0) then
				if rotatingAt*1.5 > degreesAway then
					if rotatingAt*0.5 > degreesAway then
						steer = -steer
					else
						steer = 0
					end
				end
			end
			return throttle, steer
		end

		local function Update()
			if CameraModule then
				if CameraModule.UserPanningTheCamera then
					CameraModule.UpdateTweenFunction = nil
				else
					if CameraModule.UpdateTweenFunction then
						local done = CameraModule.UpdateTweenFunction()
						if done then
							CameraModule.UpdateTweenFunction = nil
						end
					end
				end
				CameraModule:Update()
			end

			if CurrentSeatPart then
				if DrivingTo then
					local throttle, steer = getThrottleAndSteer(CurrentSeatPart, DrivingTo)
					CurrentSeatPart.Throttle = throttle
					CurrentSeatPart.Steer = steer
				end
			end
		end

		local success = pcall(function() RunService:BindToRenderStep("ClickToMoveRenderUpdate",Enum.RenderPriority.Camera.Value - 1,Update) end)
		if not success then
			if RenderSteppedConn then
				RenderSteppedConn:disconnect()
			end
			RenderSteppedConn = RunService.RenderStepped:connect(Update)
		end

		local WasAutoJumper = false
		local WasAutoJumpMobile = false
		local function onSeated(child, active, currentSeatPart)
			if active then
				if BindableEvent_EnableTouchJump then
					BindableEvent_EnableTouchJump:Fire(true)
				end
				if currentSeatPart and currentSeatPart.ClassName == "VehicleSeat" then
					CurrentSeatPart = currentSeatPart
					if AutoJumperInstance then
						AutoJumperInstance:Stop()
						AutoJumperInstance = nil
						WasAutoJumper = true
					else
						WasAutoJumper = false
					end
					if child.AutoJumpEnabled then
						WasAutoJumpMobile = true
						child.AutoJumpEnabled = false
					end
				end
			else
				CurrentSeatPart = nil
				if BindableEvent_EnableTouchJump then
					BindableEvent_EnableTouchJump:Fire(false)
				end
				if WasAutoJumper then
					AutoJumperInstance = AutoJumper()
					WasAutoJumper = false
				end
				if WasAutoJumpMobile then
					child.AutoJumpEnabled = true
					WasAutoJumpMobile = false
				end
			end
		end

		local function OnCharacterChildAdded(child)
			if UIS.TouchEnabled then
				if child:IsA('Tool') then
					child.ManualActivationOnly = true
				end
			end
			if child:IsA('Humanoid') then
				disconnectEvent(HumanoidDiedConn)
				HumanoidDiedConn = child.Died:connect(function()
					DebrisService:AddItem(ExistingIndicator, 1)
					if AutoJumperInstance then
						AutoJumperInstance:Stop()
						AutoJumperInstance = nil
					end
				end)
				local WasAutoJumper = false
				local WasAutoJumpMobile = false
				HumanoidSeatedConn = child.Seated:connect(function(active, seat) onSeated(child, active, seat) end)
				if child.SeatPart then
					onSeated(child, true, child.SeatPart)
				end
			end
		end

		CharacterChildAddedConn = character.ChildAdded:connect(function(child)
			OnCharacterChildAdded(child)
		end)
		CharacterChildRemovedConn = character.ChildRemoved:connect(function(child)
			if UIS.TouchEnabled then
				if child:IsA('Tool') then
					child.ManualActivationOnly = false
				end
			end
		end)
		for _, child in pairs(character:GetChildren()) do
			OnCharacterChildAdded(child)
		end
	end

	local Running = false

	function this:Stop()
		if Running then
			DisconnectEvents()
			CleanupPath()
			if AutoJumperInstance then
				AutoJumperInstance:Stop()
				AutoJumperInstance = nil
			end
			if CameraModule then
				CameraModule.UpdateTweenFunction = nil
				CameraModule:SetEnabled(false)
			end
			-- Restore tool activation on shutdown
			if UIS.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
			Running = false
		end
	end

	function this:Start()
		if not Running then
			if Player.Character then -- retro-listen
				OnCharacterAdded(Player.Character)
			end
			OnCharacterAddedConn = Player.CharacterAdded:connect(OnCharacterAdded)
			if CameraModule then
				CameraModule:SetEnabled(true)
			end
			Running = true
		end
	end

	return this
end

return CreateClickToMoveModule
