local Z local function R()if Z and not Z.Parent then Z=nil end if not Z then Z=Instance.new("\080\097\114\116")Z.Name="\067\108\105\099\107\084\111\077\111\118\101\080\111\112\117\112\065\100\111\114\110\101\101"Z.Transparency=1 Z.CanCollide=false Z.Anchored=true Z.Size=Vector3.new(2,2,2)Z.CFrame=CFrame.new()Z.Parent=workspace.CurrentCamera end return Z end local c={}local function k(Z)local k=Instance.new("\073\109\097\103\101\072\097\110\100\108\101\065\100\111\114\110\109\101\110\116")k.AlwaysOnTop=false k.Transparency=1 k.Size=ZERO_VECTOR2 k.SizeRelativeOffset=ZERO_VECTOR3 k.Image="\114\098\120\097\115\115\101\116\058\047\047\116\101\120\116\117\114\101\115\047\117\105\047\109\111\118\101\046\112\110\103"k.ZIndex=20 local u=0 if Z=="\068\101\115\116\105\110\097\116\105\111\110\080\111\112\117\112"then k.Color3=Color3.fromRGB(0,175,255)u=1.25 elseif Z=="\068\105\114\101\099\116\087\097\108\107\080\111\112\117\112"then k.Color3=Color3.fromRGB(0,175,255)u=1.25 elseif Z=="\070\097\105\108\117\114\101\080\111\112\117\112"then k.Color3=Color3.fromRGB(255,100,100)u=1.25 elseif Z=="\080\097\116\104\101\114\080\111\112\117\112"then k.Color3=Color3.fromRGB(255,255,255)u=1 k.ZIndex=10 end k.Size=Vector2.new(5,.1)*u local x={}x.Model=k c[#c+1]=k function x.TweenIn(Z)local R=TweenInfo.new(1.5,Enum.EasingStyle.Elastic,Enum.EasingDirection.Out)local c=TweenService:Create(k,R,{Size=Vector2.new(2,2)*u})c:Play();(TweenService:Create(k,TweenInfo.new(.25,Enum.EasingStyle.Sine,Enum.EasingDirection.InOut,0,false,.1),{Transparency=0;SizeRelativeOffset=Vector3.new(0,u*1.5,0)})):Play()return c end function x.TweenOut(Z)local R=TweenInfo.new(.25,Enum.EasingStyle.Quad,Enum.EasingDirection.In)local u=TweenService:Create(k,R,{Size=ZERO_VECTOR2})u:Play();(coroutine.wrap(function()u.Completed:Wait()for Z=1,#c,1 do if c[Z]==k then table.remove(c,Z)break end end end))()return u end function x.Place(k,Z,c)if not k.Model.Parent then local c=R()k.Model.Parent=c k.Model.Adornee=c local x=Ray.new(Z+Vector3.new(0,2.5,0),Vector3.new(0,-10,0))local I,s,i=workspace:FindPartOnRayWithIgnoreList(x,{workspace.CurrentCamera;Player.Character})k.Model.CFrame=CFrame.new(s)+Vector3.new(0,-u,0)end end return x end local function u(Z,R)local c={}local u=false local function x(Z)for R,k in pairs(c)do if R<=Z then local Z=k:TweenOut()spawn(function()Z.Completed:Wait()k.Model:Destroy()end)c[R]=nil end end end local function I()u=true x(#Z)end spawn(function()for x=1,#Z,1 do if u then break end local I=x%R==0 and(x<#Z and(Z[#Z].Position-Z[x].Position).magnitude>4)if I then local R=k("\080\097\116\104\101\114\080\111\112\117\112")c[x]=R local u=Z[x+1]R:Place(Z[x].Position,u and u.Position or Z[#Z].Position)local I=R:TweenIn()wait(.2)end end end)return I,x end local function x(Z,R,c)local k={}k.Cancelled=false k.Started=false k.Finished=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")k.PathFailed=Instance.new("\066\105\110\100\097\098\108\101\069\118\101\110\116")k.PathComputing=false k.PathComputed=false k.TargetPoint=R k.TargetSurfaceNormal=c k.DiedConn=nil k.SeatedConn=nil k.MoveToConn=nil k.BlockedConn=nil k.CurrentPoint=0 function k.Cleanup(Z)if k.stopTraverseFunc then k.stopTraverseFunc()k.stopTraverseFunc=nil end if k.MoveToConn then k.MoveToConn:Disconnect()k.MoveToConn=nil end if k.BlockedConn then k.BlockedConn:Disconnect()k.BlockedConn=nil end if k.DiedConn then k.DiedConn:Disconnect()k.DiedConn=nil end if k.SeatedConn then k.SeatedConn:Disconnect()k.SeatedConn=nil end k.humanoid=nil end function k.Cancel(Z)k.Cancelled=true k:Cleanup()end function k.OnPathInterrupted(Z)k.Cancelled=true k:OnPointReached(false)end function k.ComputePath(Z)local R=findPlayerHumanoid(Player)local c=R and R.Torso local u=false if c then if k.PathComputed or k.PathComputing then return end k.PathComputing=true u=pcall(function()k.pathResult=PathfindingService:FindPathAsync(c.CFrame.p,k.TargetPoint)end)k.pointList=k.pathResult and k.pathResult:GetWaypoints()if k.pathResult then k.BlockedConn=k.pathResult.Blocked:Connect(function(Z)k:OnPathBlocked(Z)end)end k.PathComputing=false k.PathComputed=k.pathResult and k.pathResult.Status==Enum.PathStatus.Success or false end return true end function k.IsValidPath(Z)if not k.pathResult then k:ComputePath()end return k.pathResult.Status==Enum.PathStatus.Success end k.Recomputing=false function k.OnPathBlocked(R,Z)local c=Z>=k.CurrentPoint if not c or k.Recomputing then return end k.Recomputing=true if k.stopTraverseFunc then k.stopTraverseFunc()k.stopTraverseFunc=nil end k.pathResult:ComputeAsync(k.humanoid.Torso.CFrame.p,k.TargetPoint)k.pointList=k.pathResult:GetWaypoints()k.PathComputed=k.pathResult and k.pathResult.Status==Enum.PathStatus.Success or false if SHOW_PATH then k.stopTraverseFunc,k.setPointFunc=u(k.pointList,4,true)end if k.PathComputed then k.humanoid=findPlayerHumanoid(Player)k.CurrentPoint=1 k:OnPointReached(true)else k.PathFailed:Fire()k:Cleanup()end k.Recomputing=false end function k.OnPointReached(R,Z)if Z and not k.Cancelled then local Z=k.CurrentPoint+1 if Z>#k.pointList then if k.stopTraverseFunc then k.stopTraverseFunc()end k.Finished:Fire()k:Cleanup()else local R=k.pointList[k.CurrentPoint]local c=k.pointList[Z]local u=k.humanoid:GetState()local x=u==Enum.HumanoidStateType.FallingDown or u==Enum.HumanoidStateType.Freefall or u==Enum.HumanoidStateType.Jumping if x then local Z=c.Action==Enum.PathWaypointAction.Jump if not Z and k.CurrentPoint>1 then local u=k.pointList[k.CurrentPoint-1]local x=R.Position-u.Position local I=c.Position-R.Position local s=(Vector2.new(x.x,x.z)).Unit local i=(Vector2.new(I.x,I.z)).Unit local Q=.996 Z=s:Dot(i)<Q end if Z then k.humanoid.FreeFalling:Wait()wait(.1)end end if k.setPointFunc then k.setPointFunc(Z)end if c.Action==Enum.PathWaypointAction.Jump then k.humanoid.Jump=true end k.humanoid:MoveTo(c.Position)k.CurrentPoint=Z end else k.PathFailed:Fire()k:Cleanup()end end function k.Start(Z)if CurrentSeatPart then return end k.humanoid=findPlayerHumanoid(Player)if not k.humanoid then k.PathFailed:Fire()return end if k.Started then return end k.Started=true if SHOW_PATH then k.stopTraverseFunc,k.setPointFunc=u(k.pointList,4)end if#k.pointList>0 then k.SeatedConn=k.humanoid.Seated:Connect(function(Z)k:OnPathInterrupted()end)k.DiedConn=k.humanoid.Died:Connect(function(Z)k:OnPathInterrupted()end)k.MoveToConn=k.humanoid.MoveToFinished:Connect(function(Z)k:OnPointReached(Z)end)k.CurrentPoint=1 k:OnPointReached(true)else k.PathFailed:Fire()if k.stopTraverseFunc then k.stopTraverseFunc()end end end k:ComputePath()if not k.PathComputed then local Z=k.TargetPoint+k.TargetSurfaceNormal*1.5 local R=Ray.new(Z,Vector3.new(0,-1,0)*50)local c,u=RayCastIgnoreList(workspace,R,getIgnoreList())if c then k.TargetPoint=u end k:ComputePath()end return k end


local popupAdornee
local function getPopupAdorneePart()
	--Handle the case of the adornee part getting deleted (camera changed, maybe)
	if popupAdornee and not popupAdornee.Parent then
		popupAdornee = nil
	end
	
	--If the adornee doesn't exist yet, create it
	if not popupAdornee then
		popupAdornee = Instance.new("Part")		
		popupAdornee.Name = "ClickToMovePopupAdornee"
		popupAdornee.Transparency = 1
		popupAdornee.CanCollide = false
		popupAdornee.Anchored = true
		popupAdornee.Size = Vector3.new(2, 2, 2)
		popupAdornee.CFrame = CFrame.new()
		
		popupAdornee.Parent = workspace.CurrentCamera
	end
	
	return popupAdornee
end

local activePopups = {}
local function createNewPopup(popupType)
	local newModel = Instance.new("ImageHandleAdornment")
	
	newModel.AlwaysOnTop = false
	newModel.Transparency = 1
	newModel.Size = ZERO_VECTOR2
	newModel.SizeRelativeOffset = ZERO_VECTOR3
	newModel.Image = "rbxasset://textures/ui/move.png"
	newModel.ZIndex = 20
	
	local radius = 0
	if popupType == "DestinationPopup" then
		newModel.Color3 = Color3.fromRGB(0, 175, 255)
		radius = 1.25
	elseif popupType == "DirectWalkPopup" then
		newModel.Color3 = Color3.fromRGB(0, 175, 255)
		radius = 1.25
	elseif popupType == "FailurePopup" then
		newModel.Color3 = Color3.fromRGB(255, 100, 100)
		radius = 1.25
	elseif popupType == "PatherPopup" then
		newModel.Color3 = Color3.fromRGB(255, 255, 255)
		radius = 1
		newModel.ZIndex = 10
	end
	newModel.Size = Vector2.new(5, 0.1) * radius
	
	local dataStructure = {}
	dataStructure.Model = newModel	
	
	activePopups[#activePopups + 1] = newModel
	
	function dataStructure:TweenIn()
		local tweenInfo = TweenInfo.new(1.5, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out)
		local tween1 = TweenService:Create(newModel, tweenInfo, { Size = Vector2.new(2,2) * radius })
		tween1:Play()
		TweenService:Create(newModel, TweenInfo.new(0.25, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, 0, false, 0.1), { Transparency = 0, SizeRelativeOffset = Vector3.new(0, radius * 1.5, 0) }):Play()
		return tween1
	end
	
	function dataStructure:TweenOut()
		local tweenInfo = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.In)
		local tween1 = TweenService:Create(newModel, tweenInfo, { Size = ZERO_VECTOR2 })
		tween1:Play()
		
		coroutine.wrap(function()
			tween1.Completed:Wait()
			
			for i = 1, #activePopups do
				if activePopups[i] == newModel then
					table.remove(activePopups, i)
					break
				end
			end
		end)()
		return tween1
	end
	
	function dataStructure:Place(position, dest)
		-- place the model at position
		if not self.Model.Parent then
			local popupAdorneePart = getPopupAdorneePart()
			self.Model.Parent = popupAdorneePart
			self.Model.Adornee = popupAdorneePart

			--Start the 10-stud long ray 2.5 studs above where the tap happened and point straight down to try to find
			--the actual ground position.
			local ray = Ray.new(position + Vector3.new(0, 2.5, 0), Vector3.new(0, -10, 0))
			local hitPart, hitPoint, hitNormal = workspace:FindPartOnRayWithIgnoreList(ray, { workspace.CurrentCamera, Player.Character })
			
			self.Model.CFrame = CFrame.new(hitPoint) + Vector3.new(0, -radius,0)
		end
	end
	
	return dataStructure
end

local function createPopupPath(points, numCircles)
	-- creates a path with the provided points, using the path and number of circles provided
	local popups = {}
	local stopTraversing = false
	
	local function killPopup(i)
		-- kill all popups before and at i
		for iter, v in pairs(popups) do
			if iter <= i then
				local tween = v:TweenOut()
				spawn(function()
					tween.Completed:Wait()
					v.Model:Destroy()
				end)
				popups[iter] = nil
			end
		end
	end	
	
	local function stopFunction()
		stopTraversing = true
		killPopup(#points)
	end
	
	spawn(function()
		for i = 1, #points do
			if stopTraversing then
				break
			end

			local includeWaypoint = i % numCircles == 0
			                    and i < #points
			                    and (points[#points].Position - points[i].Position).magnitude > 4
			if includeWaypoint then
				local popup = createNewPopup("PatherPopup")
				popups[i] = popup
				local nextPopup = points[i+1]
				popup:Place(points[i].Position, nextPopup and nextPopup.Position or points[#points].Position)
				local tween = popup:TweenIn()
				wait(0.2)
			end
		end
	end)
	
	return stopFunction, killPopup
end

local function Pather(character, endPoint, surfaceNormal)
	local this = {}

	this.Cancelled = false
	this.Started = false

	this.Finished = Instance.new("BindableEvent")
	this.PathFailed = Instance.new("BindableEvent")
	
	this.PathComputing = false
	this.PathComputed = false
	
	this.TargetPoint = endPoint
	this.TargetSurfaceNormal = surfaceNormal
	
	this.DiedConn = nil
	this.SeatedConn = nil
	this.MoveToConn = nil
	this.BlockedConn = nil
	this.CurrentPoint = 0

	function this:Cleanup()
		if this.stopTraverseFunc then
			this.stopTraverseFunc()
			this.stopTraverseFunc = nil
		end

		if this.MoveToConn then
			this.MoveToConn:Disconnect()
			this.MoveToConn = nil
		end

		if this.BlockedConn then
			this.BlockedConn:Disconnect()
			this.BlockedConn = nil
		end

		if this.DiedConn then
			this.DiedConn:Disconnect()
			this.DiedConn = nil
		end

		if this.SeatedConn then
			this.SeatedConn:Disconnect()
			this.SeatedConn = nil
		end

		this.humanoid = nil
	end

	function this:Cancel()
		this.Cancelled = true
		this:Cleanup()
	end

	function this:OnPathInterrupted()
		-- Stop moving
		this.Cancelled = true
		this:OnPointReached(false)
	end

	function this:ComputePath()
		local humanoid = findPlayerHumanoid(Player)
		local torso = humanoid and humanoid.Torso
		local success = false
		if torso then
			if this.PathComputed or this.PathComputing then return end
			this.PathComputing = true
			success = pcall(function()
				this.pathResult = PathfindingService:FindPathAsync(torso.CFrame.p, this.TargetPoint)
			end)
			this.pointList = this.pathResult and this.pathResult:GetWaypoints()
			if this.pathResult then
				this.BlockedConn = this.pathResult.Blocked:Connect(function(blockedIdx) this:OnPathBlocked(blockedIdx) end)
			end
			this.PathComputing = false
			this.PathComputed = this.pathResult and this.pathResult.Status == Enum.PathStatus.Success or false
		end
		return true
	end

	function this:IsValidPath()
		if not this.pathResult then
			this:ComputePath()
		end
		return this.pathResult.Status == Enum.PathStatus.Success
	end

	this.Recomputing = false
	function this:OnPathBlocked(blockedWaypointIdx)
		local pathBlocked = blockedWaypointIdx >= this.CurrentPoint
		if not pathBlocked or this.Recomputing then
			return
		end

		this.Recomputing = true

		if this.stopTraverseFunc then
			this.stopTraverseFunc()
			this.stopTraverseFunc = nil
		end

		this.pathResult:ComputeAsync(this.humanoid.Torso.CFrame.p, this.TargetPoint)
		this.pointList = this.pathResult:GetWaypoints()
		this.PathComputed = this.pathResult and this.pathResult.Status == Enum.PathStatus.Success or false

		if SHOW_PATH then
			this.stopTraverseFunc, this.setPointFunc = createPopupPath(this.pointList, 4, true)
		end
		if this.PathComputed then
			this.humanoid = findPlayerHumanoid(Player)
			this.CurrentPoint = 1 -- The first waypoint is always the start location. Skip it.
			this:OnPointReached(true) -- Move to first point
		else
			this.PathFailed:Fire()
			this:Cleanup()
		end

		this.Recomputing = false
	end

	function this:OnPointReached(reached)

		if reached and not this.Cancelled then

			local nextWaypointIdx = this.CurrentPoint + 1

			if nextWaypointIdx > #this.pointList then
				-- End of path reached
				if this.stopTraverseFunc then
					this.stopTraverseFunc()
				end
				this.Finished:Fire()
				this:Cleanup()
			else
				local currentWaypoint = this.pointList[this.CurrentPoint]
				local nextWaypoint = this.pointList[nextWaypointIdx]

				-- If airborne, only allow to keep moving
				-- if nextWaypoint.Action ~= Jump, or path mantains a direction
				-- Otherwise, wait until the humanoid gets to the ground
				local currentState = this.humanoid:GetState()
				local isInAir = currentState == Enum.HumanoidStateType.FallingDown
					or currentState == Enum.HumanoidStateType.Freefall
					or currentState == Enum.HumanoidStateType.Jumping
				
				if isInAir then
					local shouldWaitForGround = nextWaypoint.Action == Enum.PathWaypointAction.Jump
					if not shouldWaitForGround and this.CurrentPoint > 1 then
						local prevWaypoint = this.pointList[this.CurrentPoint - 1]

						local prevDir = currentWaypoint.Position - prevWaypoint.Position
						local currDir = nextWaypoint.Position - currentWaypoint.Position

						local prevDirXZ = Vector2.new(prevDir.x, prevDir.z).Unit
						local currDirXZ = Vector2.new(currDir.x, currDir.z).Unit

						local THRESHOLD_COS = 0.996 -- ~cos(5 degrees)
						shouldWaitForGround = prevDirXZ:Dot(currDirXZ) < THRESHOLD_COS
					end

					if shouldWaitForGround then
						this.humanoid.FreeFalling:Wait()

						-- Give time to the humanoid's state to change
						-- Otherwise, the jump flag in Humanoid
						-- will be reset by the state change
						wait(0.1)
					end
				end

				-- Move to the next point
				if this.setPointFunc then
					this.setPointFunc(nextWaypointIdx)
				end
				
				if nextWaypoint.Action == Enum.PathWaypointAction.Jump then
					this.humanoid.Jump = true
				end
				this.humanoid:MoveTo(nextWaypoint.Position)

				this.CurrentPoint = nextWaypointIdx
			end
		else
			this.PathFailed:Fire()
			this:Cleanup()
		end
	end

	function this:Start()
		if CurrentSeatPart then
			return
		end
		
		this.humanoid = findPlayerHumanoid(Player)
		if not this.humanoid then
			this.PathFailed:Fire()
			return
		end

		if this.Started then return end
		this.Started = true
		
		if SHOW_PATH then
			-- choose whichever one Mike likes best
			this.stopTraverseFunc, this.setPointFunc = createPopupPath(this.pointList, 4)
		end

		if #this.pointList > 0 then
			this.SeatedConn = this.humanoid.Seated:Connect(function(reached) this:OnPathInterrupted() end)
			this.DiedConn = this.humanoid.Died:Connect(function(reached) this:OnPathInterrupted() end)
			this.MoveToConn = this.humanoid.MoveToFinished:Connect(function(reached) this:OnPointReached(reached) end)

			this.CurrentPoint = 1 -- The first waypoint is always the start location. Skip it.
			this:OnPointReached(true) -- Move to first point
		else
			this.PathFailed:Fire()
			if this.stopTraverseFunc then
				this.stopTraverseFunc()
			end
		end
	end
	
	this:ComputePath()
	if not this.PathComputed then
		-- set the end point towards the camera and raycasted towards the ground in case we hit a wall
		local offsetPoint = this.TargetPoint + this.TargetSurfaceNormal*1.5
		local ray = Ray.new(offsetPoint, Vector3.new(0,-1,0)*50)
		local newHitPart, newHitPos = RayCastIgnoreList(workspace, ray, getIgnoreList())
		if newHitPart then
			this.TargetPoint = newHitPos
		end
		-- try again
		this:ComputePath()
	end
	
	return this
end
