local function b(b)local O=Instance.new("\073\109\097\103\101\072\097\110\100\108\101\065\100\111\114\110\109\101\110\116")O.AlwaysOnTop=false O.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"O.ZIndex=2 local I=ZERO_VECTOR2 if b=="\068\101\115\116\105\110\097\116\105\111\110\080\111\112\117\112"then O.Color3=Color3.fromRGB(0,175,255)I=Vector2.new(4,4)elseif b=="\068\105\114\101\099\116\087\097\108\107\080\111\112\117\112"then O.Color3=Color3.fromRGB(0,175,255)I=Vector2.new(4,4)elseif b=="\070\097\105\108\117\114\101\080\111\112\117\112"then O.Color3=Color3.fromRGB(255,100,100)I=Vector2.new(4,4)elseif b=="\080\097\116\104\101\114\080\111\112\117\112"then O.Color3=Color3.fromRGB(255,255,255)I=Vector2.new(3,3)O.ZIndex=1 end local T={}T.Model=O function T.TweenIn(b)local O=tweenService:Create(b.Model,TweenInfo.new(1,Enum.EasingStyle.Elastic,Enum.EasingDirection.Out,0,false,0),{Size=I})O:Play()return O end function T.TweenOut(b)local O=tweenService:Create(b.Model,TweenInfo.new(.25,Enum.EasingStyle.Quad,Enum.EasingDirection.In,0,false,0),{Size=ZERO_VECTOR2})O:Play()return O end function T.Place(I,b,O)if not I.Model.Parent then I.Model.Parent=workspace.Terrain I.Model.Adornee=workspace.Terrain local O=Ray.new(b+Vector3.new(0,2.5,0),Vector3.new(0,-10,0))local T,C,H=workspace:FindPartOnRayWithIgnoreList(O,{workspace.CurrentCamera,Player.Character})I.Model.CFrame=CFrame.new(C,C+H)*CFrame.Angles(0,0,0)+Vector3.new(0,.2,0)end end return T end local function O(O,I)local T={}local C=false local function H(b)for O,I in pairs(T)do if O<=b then local b=I:TweenOut()spawn(function()b.Completed:wait()I.Model:Destroy()end)T[O]=nil end end end local function y()C=true H(#O)end spawn(function()for H=1,#O,1 do if C then break end local y=H%I==0 and(H<#O and(O[#O].Position-O[H].Position).magnitude>4)if y then local I=b("\080\097\116\104\101\114\080\111\112\117\112")T[H]=I local C=O[H+1]I:Place(O[H].Position,C and C.Position or O[#O].Position)local y=I:TweenIn()wait(.2)end end end)return y,H end local function I(b,I,T)local C={}C.Cancelled=false C.Started=false C.Finished=Signal.Create()C.PathFailed=Signal.Create()C.PathStarted=Signal.Create()C.PathComputing=false C.PathComputed=false C.TargetPoint=I C.TargetSurfaceNormal=T C.MoveToConn=nil C.CurrentPoint=0 function C.Cleanup(b)if C.stopTraverseFunc then C.stopTraverseFunc()end if C.MoveToConn then C.MoveToConn:disconnect()C.MoveToConn=nil C.humanoid=nil end C.humanoid=nil end function C.Cancel(b)C.Cancelled=true C:Cleanup()end function C.ComputePath(b)local O=findPlayerHumanoid(Player)local I=O and O.Torso local T=false if I then if C.PathComputed or C.PathComputing then return end C.PathComputing=true T=pcall(function()C.pathResult=PathfindingService:FindPathAsync(I.CFrame.p,C.TargetPoint)end)C.pointList=C.pathResult and C.pathResult:GetWaypoints()C.PathComputing=false C.PathComputed=C.pathResult and C.pathResult.Status==Enum.PathStatus.Success or false end return true end function C.IsValidPath(b)if not C.pathResult then C:ComputePath()end return C.pathResult.Status==Enum.PathStatus.Success end function C.OnPointReached(O,b)if b and not C.Cancelled then C.CurrentPoint=C.CurrentPoint+1 if C.CurrentPoint>#C.pointList then if C.stopTraverseFunc then C.stopTraverseFunc()end C.Finished:fire()C:Cleanup()else if C.CurrentPoint+1<=#C.pointList then local b=C.pointList[C.CurrentPoint+1].Action if b==Enum.PathWaypointAction.Jump then local b=C.humanoid:GetState()if b==Enum.HumanoidStateType.FallingDown or b==Enum.HumanoidStateType.Freefall or b==Enum.HumanoidStateType.Jumping then C.humanoid.FreeFalling:wait()wait(.1)end end end if C.setPointFunc then C.setPointFunc(C.CurrentPoint)end local b=C.pointList[C.CurrentPoint]if b.Action==Enum.PathWaypointAction.Jump then C.humanoid.Jump=true end C.humanoid:MoveTo(b.Position)end else C.PathFailed:fire()C:Cleanup()end end function C.Start(b)if CurrentSeatPart then return end C.humanoid=findPlayerHumanoid(Player)if C.Started then return end C.Started=true if SHOW_PATH then C.stopTraverseFunc,C.setPointFunc=O(C.pointList,4)end if#C.pointList>0 then C.MoveToConn=C.humanoid.MoveToFinished:connect(function(b)C:OnPointReached(b)end)C.CurrentPoint=1 C:OnPointReached(true)else C.PathFailed:fire()if C.stopTraverseFunc then C.stopTraverseFunc()end end end C:ComputePath()if not C.PathComputed then local b=C.TargetPoint+C.TargetSurfaceNormal*1.5 local O=Ray.new(b,Vector3_new(0,-1,0)*50)local I,T=RayCastIgnoreList(workspace,O,getIgnoreList())if I then C.TargetPoint=T end C:ComputePath()end return C end


local function createNewPopup(popupType)	
	
	local newModel = Instance.new("ImageHandleAdornment")
	
	newModel.AlwaysOnTop = false
	newModel.Image = "rbxasset://textures/ui/move.png"
	newModel.ZIndex = 2
	
	local size = ZERO_VECTOR2
	if popupType == "DestinationPopup" then
		newModel.Color3 = Color3.fromRGB(0, 175, 255)
		size = Vector2.new(4,4)
	elseif popupType == "DirectWalkPopup" then
		newModel.Color3 = Color3.fromRGB(0, 175, 255)
		size = Vector2.new(4,4)
	elseif popupType == "FailurePopup" then
		newModel.Color3 = Color3.fromRGB(255, 100, 100)
		size = Vector2.new(4,4)
	elseif popupType == "PatherPopup" then
		newModel.Color3 = Color3.fromRGB(255, 255, 255)
		size = Vector2.new(3,3)
		newModel.ZIndex = 1
	end
	
	local dataStructure = {}
	dataStructure.Model = newModel	
	
	function dataStructure:TweenIn()
		local tween1 = tweenService:Create(self.Model,
			TweenInfo.new(
				1,
				Enum.EasingStyle.Elastic,
				Enum.EasingDirection.Out,
				0,
				false,
				0
			),{
				Size = size
			}	
		)
		tween1:Play()
		return tween1
	end
	
	function dataStructure:TweenOut()
		local tween1 = tweenService:Create(self.Model,
			TweenInfo.new(
				.25,
				Enum.EasingStyle.Quad,
				Enum.EasingDirection.In,
				0,
				false,
				0
			),{
				Size = ZERO_VECTOR2
			}	
		)
		tween1:Play()
		return tween1
	end
	
	function dataStructure:Place(position, dest)
		-- place the model at position
		if not self.Model.Parent then
			self.Model.Parent = workspace.Terrain
			self.Model.Adornee = workspace.Terrain

			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, hitPoint + hitNormal) * CFrame.Angles(0,0,0) + Vector3.new(0, 0.2,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 = Signal.Create()
	this.PathFailed = Signal.Create()
	this.PathStarted = Signal.Create()
	
	this.PathComputing = false
	this.PathComputed = false
	
	this.TargetPoint = endPoint
	this.TargetSurfaceNormal = surfaceNormal
	
	this.MoveToConn = nil
	this.CurrentPoint = 0

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

		if this.MoveToConn then
			this.MoveToConn:disconnect()
			this.MoveToConn = nil
			this.humanoid = nil
		end

		this.humanoid = nil
	end

	function this:Cancel()
		this.Cancelled = true
		this:Cleanup()
	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()
			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

	function this:OnPointReached(reached)

		if reached and not this.Cancelled then

			this.CurrentPoint = this.CurrentPoint + 1

			if this.CurrentPoint > #this.pointList then
				-- End of path reached
				if this.stopTraverseFunc then
					this.stopTraverseFunc()
				end
				this.Finished:fire()
				this:Cleanup()
			else
				-- If next action == Jump, but the humanoid
				-- is still jumping from a previous action
				-- wait until it gets to the ground
				if this.CurrentPoint + 1 <= #this.pointList then
					local nextAction = this.pointList[this.CurrentPoint + 1].Action
					if nextAction == Enum.PathWaypointAction.Jump then
						local currentState = this.humanoid:GetState()
						if currentState == Enum.HumanoidStateType.FallingDown or
						   currentState == Enum.HumanoidStateType.Freefall or
						   currentState == Enum.HumanoidStateType.Jumping 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
				end

				-- Move to the next point
				if this.setPointFunc then
					this.setPointFunc(this.CurrentPoint)
				end

				local nextWaypoint = this.pointList[this.CurrentPoint]
				
				if nextWaypoint.Action == Enum.PathWaypointAction.Jump then
					this.humanoid.Jump = true
				end
				this.humanoid:MoveTo(nextWaypoint.Position)
			end
		else
			this.PathFailed:fire()
			this:Cleanup()
		end
	end

	function this:Start()
		if CurrentSeatPart then
			return
		end
		
		this.humanoid = findPlayerHumanoid(Player)
		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.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
