--!nonstrict
local ClickToMoveDisplay = {}

local FAILURE_ANIMATION_ID = "rbxassetid://2874840706"

local TrailDotIcon = "rbxasset://textures/ui/traildot.png"
local EndWaypointIcon = "rbxasset://textures/ui/waypoint.png"

local WaypointsAlwaysOnTop = false

local WAYPOINT_INCLUDE_FACTOR = 2
local LAST_DOT_DISTANCE = 3

local WAYPOINT_BILLBOARD_SIZE = UDim2.new(0, 1.68 * 25, 0, 2 * 25)

local ENDWAYPOINT_SIZE_OFFSET_MIN = Vector2.new(0, 0.5)
local ENDWAYPOINT_SIZE_OFFSET_MAX = Vector2.new(0, 1)

local FAIL_WAYPOINT_SIZE_OFFSET_CENTER = Vector2.new(0, 0.5)
local FAIL_WAYPOINT_SIZE_OFFSET_LEFT = Vector2.new(0.1, 0.5)
local FAIL_WAYPOINT_SIZE_OFFSET_RIGHT = Vector2.new(-0.1, 0.5)

local FAILURE_TWEEN_LENGTH = 0.125
local FAILURE_TWEEN_COUNT = 4

local TWEEN_WAYPOINT_THRESHOLD = 5

local TRAIL_DOT_PARENT_NAME = "ClickToMoveDisplay"

local TrailDotSize = Vector2.new(1.5, 1.5)

local TRAIL_DOT_MIN_SCALE = 1
local TRAIL_DOT_MIN_DISTANCE = 10
local TRAIL_DOT_MAX_SCALE = 2.5
local TRAIL_DOT_MAX_DISTANCE = 100

local PlayersService = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")

local LocalPlayer = PlayersService.LocalPlayer

local function CreateWaypointTemplates()
	local TrailDotTemplate = Instance.new("Part")
	TrailDotTemplate.Size = Vector3.new(1, 1, 1)
	TrailDotTemplate.Anchored = true
	TrailDotTemplate.CanCollide = false
	TrailDotTemplate.Name = "TrailDot"
	TrailDotTemplate.Transparency = 1
	local TrailDotImage = Instance.new("ImageHandleAdornment")
	TrailDotImage.Name = "TrailDotImage"
	TrailDotImage.Size = TrailDotSize
	TrailDotImage.SizeRelativeOffset = Vector3.new(0, 0, -0.1)
	TrailDotImage.AlwaysOnTop = WaypointsAlwaysOnTop
	TrailDotImage.Image = TrailDotIcon
	TrailDotImage.Adornee = TrailDotTemplate
	TrailDotImage.Parent = TrailDotTemplate

	local EndWaypointTemplate = Instance.new("Part")
	EndWaypointTemplate.Size = Vector3.new(2, 2, 2)
	EndWaypointTemplate.Anchored = true
	EndWaypointTemplate.CanCollide = false
	EndWaypointTemplate.Name = "EndWaypoint"
	EndWaypointTemplate.Transparency = 1
	local EndWaypointImage = Instance.new("ImageHandleAdornment")
	EndWaypointImage.Name = "TrailDotImage"
	EndWaypointImage.Size = TrailDotSize
	EndWaypointImage.SizeRelativeOffset = Vector3.new(0, 0, -0.1)
	EndWaypointImage.AlwaysOnTop = WaypointsAlwaysOnTop
	EndWaypointImage.Image = TrailDotIcon
	EndWaypointImage.Adornee = EndWaypointTemplate
	EndWaypointImage.Parent = EndWaypointTemplate
	local EndWaypointBillboard = Instance.new("BillboardGui")
	EndWaypointBillboard.Name = "EndWaypointBillboard"
	EndWaypointBillboard.Size = WAYPOINT_BILLBOARD_SIZE
	EndWaypointBillboard.LightInfluence = 0
	EndWaypointBillboard.SizeOffset = ENDWAYPOINT_SIZE_OFFSET_MIN
	EndWaypointBillboard.AlwaysOnTop = true
	EndWaypointBillboard.Adornee = EndWaypointTemplate
	EndWaypointBillboard.Parent = EndWaypointTemplate
	local EndWaypointImageLabel = Instance.new("ImageLabel")
	EndWaypointImageLabel.Image = EndWaypointIcon
	EndWaypointImageLabel.BackgroundTransparency = 1
	EndWaypointImageLabel.Size = UDim2.new(1, 0, 1, 0)
	EndWaypointImageLabel.Parent = EndWaypointBillboard

	local FailureWaypointTemplate = Instance.new("Part")
	FailureWaypointTemplate.Size = Vector3.new(2, 2, 2)
	FailureWaypointTemplate.Anchored = true
	FailureWaypointTemplate.CanCollide = false
	FailureWaypointTemplate.Name = "FailureWaypoint"
	FailureWaypointTemplate.Transparency = 1
	local FailureWaypointImage = Instance.new("ImageHandleAdornment")
	FailureWaypointImage.Name = "TrailDotImage"
	FailureWaypointImage.Size = TrailDotSize
	FailureWaypointImage.SizeRelativeOffset = Vector3.new(0, 0, -0.1)
	FailureWaypointImage.AlwaysOnTop = WaypointsAlwaysOnTop
	FailureWaypointImage.Image = TrailDotIcon
	FailureWaypointImage.Adornee = FailureWaypointTemplate
	FailureWaypointImage.Parent = FailureWaypointTemplate
	local FailureWaypointBillboard = Instance.new("BillboardGui")
	FailureWaypointBillboard.Name = "FailureWaypointBillboard"
	FailureWaypointBillboard.Size = WAYPOINT_BILLBOARD_SIZE
	FailureWaypointBillboard.LightInfluence = 0
	FailureWaypointBillboard.SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_CENTER
	FailureWaypointBillboard.AlwaysOnTop = true
	FailureWaypointBillboard.Adornee = FailureWaypointTemplate
	FailureWaypointBillboard.Parent = FailureWaypointTemplate
	local FailureWaypointFrame = Instance.new("Frame")
	FailureWaypointFrame.BackgroundTransparency = 1
	FailureWaypointFrame.Size = UDim2.new(0, 0, 0, 0)
	FailureWaypointFrame.Position = UDim2.new(0.5, 0, 1, 0)
	FailureWaypointFrame.Parent = FailureWaypointBillboard
	local FailureWaypointImageLabel = Instance.new("ImageLabel")
	FailureWaypointImageLabel.Image = EndWaypointIcon
	FailureWaypointImageLabel.BackgroundTransparency = 1
	FailureWaypointImageLabel.Position = UDim2.new(
		0, -WAYPOINT_BILLBOARD_SIZE.X.Offset/2, 0, -WAYPOINT_BILLBOARD_SIZE.Y.Offset
	)
	FailureWaypointImageLabel.Size = WAYPOINT_BILLBOARD_SIZE
	FailureWaypointImageLabel.Parent = FailureWaypointFrame

	return TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate
end

local TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()

local function getTrailDotParent()
	local camera = Workspace.CurrentCamera
	local trailParent = camera:FindFirstChild(TRAIL_DOT_PARENT_NAME)
	if not trailParent then
		trailParent = Instance.new("Model")
		trailParent.Name = TRAIL_DOT_PARENT_NAME
		trailParent.Parent = camera
	end
	return trailParent
end

local function placePathWaypoint(waypointModel, position: Vector3)
	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, LocalPlayer.Character }
	)
	if hitPart then
		waypointModel.CFrame = CFrame.new(hitPoint, hitPoint + hitNormal)
		waypointModel.Parent = getTrailDotParent()
	end
end

local TrailDot = {}
TrailDot.__index = TrailDot

function TrailDot:Destroy()
	self.DisplayModel:Destroy()
end

function TrailDot:NewDisplayModel(position)
	local newDisplayModel: Part = TrailDotTemplate:Clone()
	placePathWaypoint(newDisplayModel, position)
	return newDisplayModel
end

function TrailDot.new(position, closestWaypoint)
	local self = setmetatable({}, TrailDot)

	self.DisplayModel = self:NewDisplayModel(position)
	self.ClosestWayPoint = closestWaypoint

	return self
end

local EndWaypoint = {}
EndWaypoint.__index = EndWaypoint

function EndWaypoint:Destroy()
	self.Destroyed = true
	self.Tween:Cancel()
	self.DisplayModel:Destroy()
end

function EndWaypoint:NewDisplayModel(position)
	local newDisplayModel: Part = EndWaypointTemplate:Clone()
	placePathWaypoint(newDisplayModel, position)
	return newDisplayModel
end

function EndWaypoint:CreateTween()
	local tweenInfo = TweenInfo.new(0.5, Enum.EasingStyle.Sine, Enum.EasingDirection.Out, -1, true)
	local tween = TweenService:Create(
		self.DisplayModel.EndWaypointBillboard,
		tweenInfo,
		{ SizeOffset = ENDWAYPOINT_SIZE_OFFSET_MAX }
	)
	tween:Play()
	return tween
end

function EndWaypoint:TweenInFrom(originalPosition: Vector3)
	local currentPositon: Vector3 = self.DisplayModel.Position
	local studsOffset = originalPosition - currentPositon
	self.DisplayModel.EndWaypointBillboard.StudsOffset = Vector3.new(0, studsOffset.Y, 0)
	local tweenInfo = TweenInfo.new(1, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
	local tween = TweenService:Create(
		self.DisplayModel.EndWaypointBillboard,
		tweenInfo,
		{ StudsOffset = Vector3.new(0, 0, 0) }
	)
	tween:Play()
	return tween
end

function EndWaypoint.new(position: Vector3, closestWaypoint: number?, originalPosition: Vector3?)
	local self = setmetatable({}, EndWaypoint)

	self.DisplayModel = self:NewDisplayModel(position)
	self.Destroyed = false
	if originalPosition and (originalPosition - position).Magnitude > TWEEN_WAYPOINT_THRESHOLD then
		self.Tween = self:TweenInFrom(originalPosition)
		coroutine.wrap(function()
			self.Tween.Completed:Wait()
			if not self.Destroyed then
				self.Tween = self:CreateTween()
			end
		end)()
	else
		self.Tween = self:CreateTween()
	end
	self.ClosestWayPoint = closestWaypoint

	return self
end

local FailureWaypoint = {}
FailureWaypoint.__index = FailureWaypoint

function FailureWaypoint:Hide()
	self.DisplayModel.Parent = nil
end

function FailureWaypoint:Destroy()
	self.DisplayModel:Destroy()
end

function FailureWaypoint:NewDisplayModel(position)
	local newDisplayModel: Part = FailureWaypointTemplate:Clone()
	placePathWaypoint(newDisplayModel, 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, LocalPlayer.Character }
	)
	if hitPart then
		newDisplayModel.CFrame = CFrame.new(hitPoint, hitPoint + hitNormal)
		newDisplayModel.Parent = getTrailDotParent()
	end
	return newDisplayModel
end

function FailureWaypoint:RunFailureTween()
	task.wait(FAILURE_TWEEN_LENGTH) -- Delay one tween length betfore starting tweening
	-- Tween out from center
	local tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH/2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
	local tweenLeft = TweenService:Create(self.DisplayModel.FailureWaypointBillboard, tweenInfo,
		{ SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_LEFT })
	tweenLeft:Play()

	local tweenLeftRoation = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
		{ Rotation = 10 })
	tweenLeftRoation:Play()

	tweenLeft.Completed:Wait()

	-- Tween back and forth
	tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH, Enum.EasingStyle.Sine, Enum.EasingDirection.Out,
		FAILURE_TWEEN_COUNT - 1, true)
	local tweenSideToSide = TweenService:Create(self.DisplayModel.FailureWaypointBillboard, tweenInfo,
		{ SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_RIGHT})
	tweenSideToSide:Play()

	-- Tween flash dark and roate left and right
	tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH, Enum.EasingStyle.Sine, Enum.EasingDirection.Out,
		FAILURE_TWEEN_COUNT - 1, true)
	local tweenFlash = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame.ImageLabel, tweenInfo,
		{ ImageColor3 = Color3.new(0.75, 0.75, 0.75)})
	tweenFlash:Play()

	local tweenRotate = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
		{ Rotation = -10 })
	tweenRotate:Play()

	tweenSideToSide.Completed:wait()

	-- Tween back to center
	tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH/2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
	local tweenCenter = TweenService:Create(self.DisplayModel.FailureWaypointBillboard, tweenInfo,
		{ SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_CENTER })
	tweenCenter:Play()

	local tweenRoation = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
		{ Rotation = 0 })
	tweenRoation:Play()

	tweenCenter.Completed:wait()

	task.wait(FAILURE_TWEEN_LENGTH) -- Delay one tween length betfore removing
end

function FailureWaypoint.new(position)
	local self = setmetatable({}, FailureWaypoint)

	self.DisplayModel = self:NewDisplayModel(position)

	return self
end

local failureAnimation = Instance.new("Animation")
failureAnimation.AnimationId = FAILURE_ANIMATION_ID

local lastHumanoid = nil
local lastFailureAnimationTrack: AnimationTrack? = nil

local function getFailureAnimationTrack(myHumanoid)
	if myHumanoid == lastHumanoid then
		return lastFailureAnimationTrack
	end
	lastFailureAnimationTrack = myHumanoid:LoadAnimation(failureAnimation)
	assert(lastFailureAnimationTrack, "")
	lastFailureAnimationTrack.Priority = Enum.AnimationPriority.Action
	lastFailureAnimationTrack.Looped = false
	return lastFailureAnimationTrack
end

local function findPlayerHumanoid()
	local character = LocalPlayer.Character
	if character then
		return character:FindFirstChildWhichIsA("Humanoid")
	end

	return
end

local function createTrailDots(wayPoints: {PathWaypoint}, originalEndWaypoint: Vector3)
	local newTrailDots = {}
	local count = 1
	for i = 1, #wayPoints - 1 do
		local closeToEnd = (wayPoints[i].Position - wayPoints[#wayPoints].Position).Magnitude < LAST_DOT_DISTANCE
		local includeWaypoint = i % WAYPOINT_INCLUDE_FACTOR == 0 and not closeToEnd
		if includeWaypoint then
			local trailDot = TrailDot.new(wayPoints[i].Position, i)
			newTrailDots[count] = trailDot
			count = count + 1
		end
	end

	local newEndWaypoint = EndWaypoint.new(wayPoints[#wayPoints].Position, #wayPoints, originalEndWaypoint)
	table.insert(newTrailDots, newEndWaypoint)

	local reversedTrailDots = {}
	count = 1
	for i = #newTrailDots, 1, -1 do
		reversedTrailDots[count] = newTrailDots[i]
		count = count + 1
	end
	return reversedTrailDots
end

local function getTrailDotScale(distanceToCamera: number, defaultSize: Vector2)
	local rangeLength = TRAIL_DOT_MAX_DISTANCE - TRAIL_DOT_MIN_DISTANCE
	local inRangePoint = math.clamp(distanceToCamera - TRAIL_DOT_MIN_DISTANCE, 0, rangeLength)/rangeLength
	local scale = TRAIL_DOT_MIN_SCALE + (TRAIL_DOT_MAX_SCALE - TRAIL_DOT_MIN_SCALE)*inRangePoint
	return defaultSize * scale
end

local createPathCount = 0
-- originalEndWaypoint is optional, causes the waypoint to tween from that position.
function ClickToMoveDisplay.CreatePathDisplay(wayPoints, originalEndWaypoint)
	createPathCount = createPathCount + 1
	local trailDots = createTrailDots(wayPoints, originalEndWaypoint)

	local function removePathBeforePoint(wayPointNumber)
		-- kill all trailDots before and at wayPointNumber
		for i = #trailDots, 1, -1 do
			local trailDot = trailDots[i]
			if trailDot.ClosestWayPoint <= wayPointNumber then
				trailDot:Destroy()
				trailDots[i] = nil
			else
				break
			end
		end
	end

	local reiszeTrailDotsUpdateName = "ClickToMoveResizeTrail" ..createPathCount
	local function resizeTrailDots()
		if #trailDots == 0 then
			RunService:UnbindFromRenderStep(reiszeTrailDotsUpdateName)
			return
		end
		local cameraPos = Workspace.CurrentCamera.CFrame.p
		for i = 1, #trailDots do
			local trailDotImage: ImageHandleAdornment = trailDots[i].DisplayModel:FindFirstChild("TrailDotImage")
			if trailDotImage then
				local distanceToCamera = (trailDots[i].DisplayModel.Position - cameraPos).Magnitude
				trailDotImage.Size = getTrailDotScale(distanceToCamera, TrailDotSize)
			end
		end
	end
	RunService:BindToRenderStep(reiszeTrailDotsUpdateName, Enum.RenderPriority.Camera.Value - 1, resizeTrailDots)

	local function removePath()
		removePathBeforePoint(#wayPoints)
	end

	return removePath, removePathBeforePoint
end

local lastFailureWaypoint = nil
function ClickToMoveDisplay.DisplayFailureWaypoint(position)
	if lastFailureWaypoint then
		lastFailureWaypoint:Hide()
	end
	local failureWaypoint = FailureWaypoint.new(position)
	lastFailureWaypoint = failureWaypoint
	coroutine.wrap(function()
		failureWaypoint:RunFailureTween()
		failureWaypoint:Destroy()
		failureWaypoint = nil
	end)()
end

function ClickToMoveDisplay.CreateEndWaypoint(position)
	return EndWaypoint.new(position)
end

function ClickToMoveDisplay.PlayFailureAnimation()
	local myHumanoid = findPlayerHumanoid()
	if myHumanoid then
		local animationTrack = getFailureAnimationTrack(myHumanoid)
		animationTrack:Play()
	end
end

function ClickToMoveDisplay.CancelFailureAnimation()
	if lastFailureAnimationTrack ~= nil and lastFailureAnimationTrack.IsPlaying then
		lastFailureAnimationTrack:Stop()
	end
end

function ClickToMoveDisplay.SetWaypointTexture(texture)
	TrailDotIcon = texture
	TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
end

function ClickToMoveDisplay.GetWaypointTexture()
	return TrailDotIcon
end

function ClickToMoveDisplay.SetWaypointRadius(radius)
	TrailDotSize = Vector2.new(radius, radius)
	TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
end

function ClickToMoveDisplay.GetWaypointRadius()
	return TrailDotSize.X
end

function ClickToMoveDisplay.SetEndWaypointTexture(texture)
	EndWaypointIcon = texture
	TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
end

function ClickToMoveDisplay.GetEndWaypointTexture()
	return EndWaypointIcon
end

function ClickToMoveDisplay.SetWaypointsAlwaysOnTop(alwaysOnTop)
	WaypointsAlwaysOnTop = alwaysOnTop
	TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
end

function ClickToMoveDisplay.GetWaypointsAlwaysOnTop()
	return WaypointsAlwaysOnTop
end

return ClickToMoveDisplay


--!nonstrict
--[[
	Gamepad Character Control - This module handles controlling your avatar using a game console-style controller

	2018 PlayerScripts Update - AllYourBlox
--]]

local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")

--[[ Constants ]]--
local ZERO_VECTOR3 = Vector3.new(0,0,0)
local NONE = Enum.UserInputType.None
local thumbstickDeadzone = 0.2

--[[ The Module ]]--
local BaseCharacterController = require(script.Parent:WaitForChild("BaseCharacterController"))
local Gamepad = setmetatable({}, BaseCharacterController)
Gamepad.__index = Gamepad

function Gamepad.new(CONTROL_ACTION_PRIORITY)
	local self = setmetatable(BaseCharacterController.new() :: any, Gamepad)

	self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY

	self.forwardValue  = 0
	self.backwardValue = 0
	self.leftValue = 0
	self.rightValue = 0

	self.activeGamepad = NONE	-- Enum.UserInputType.Gamepad1, 2, 3...
	self.gamepadConnectedConn = nil
	self.gamepadDisconnectedConn = nil
	return self
end

function Gamepad:Enable(enable: boolean): boolean
	if not UserInputService.GamepadEnabled then
		return false
	end

	if enable == self.enabled then
		-- Module is already in the state being requested. True is returned here since the module will be in the state
		-- expected by the code that follows the Enable() call. This makes more sense than returning false to indicate
		-- no action was necessary. False indicates failure to be in requested/expected state.
		return true
	end

	self.forwardValue  = 0
	self.backwardValue = 0
	self.leftValue = 0
	self.rightValue = 0
	self.moveVector = ZERO_VECTOR3
	self.isJumping = false

	if enable then
		self.activeGamepad = self:GetHighestPriorityGamepad()
		if self.activeGamepad ~= NONE then
			self:BindContextActions()
			self:ConnectGamepadConnectionListeners()
		else
			-- No connected gamepads, failure to enable
			return false
		end
	else
		self:UnbindContextActions()
		self:DisconnectGamepadConnectionListeners()
		self.activeGamepad = NONE
	end

	self.enabled = enable
	return true
end

-- This function selects the lowest number gamepad from the currently-connected gamepad
-- and sets it as the active gamepad
function Gamepad:GetHighestPriorityGamepad()
	local connectedGamepads = UserInputService:GetConnectedGamepads()
	local bestGamepad = NONE -- Note that this value is higher than all valid gamepad values
	for _, gamepad in pairs(connectedGamepads) do
		if gamepad.Value < bestGamepad.Value then
			bestGamepad = gamepad
		end
	end
	return bestGamepad
end

function Gamepad:BindContextActions()

	if self.activeGamepad == NONE then
		-- There must be an active gamepad to set up bindings
		return false
	end

	local handleJumpAction = function(actionName, inputState, inputObject)
		self.isJumping = (inputState == Enum.UserInputState.Begin)
		return Enum.ContextActionResult.Sink
	end

	local handleThumbstickInput = function(actionName, inputState, inputObject)

		if inputState == Enum.UserInputState.Cancel then
			self.moveVector = ZERO_VECTOR3
			return Enum.ContextActionResult.Sink
		end

		if self.activeGamepad ~= inputObject.UserInputType then
			return Enum.ContextActionResult.Pass
		end
		if inputObject.KeyCode ~= Enum.KeyCode.Thumbstick1 then return end

		if inputObject.Position.magnitude > thumbstickDeadzone then
			self.moveVector  =  Vector3.new(inputObject.Position.X, 0, -inputObject.Position.Y)
		else
			self.moveVector = ZERO_VECTOR3
		end
		return Enum.ContextActionResult.Sink
	end

	ContextActionService:BindActivate(self.activeGamepad, Enum.KeyCode.ButtonR2)
	ContextActionService:BindActionAtPriority("jumpAction", handleJumpAction, false,
		self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.ButtonA)
	ContextActionService:BindActionAtPriority("moveThumbstick", handleThumbstickInput, false,
		self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.Thumbstick1)

	return true
end

function Gamepad:UnbindContextActions()
	if self.activeGamepad ~= NONE then
		ContextActionService:UnbindActivate(self.activeGamepad, Enum.KeyCode.ButtonR2)
	end
	ContextActionService:UnbindAction("moveThumbstick")
	ContextActionService:UnbindAction("jumpAction")
end

function Gamepad:OnNewGamepadConnected()
	-- A new gamepad has been connected.
	local bestGamepad: Enum.UserInputType = self:GetHighestPriorityGamepad()

	if bestGamepad == self.activeGamepad then
		-- A new gamepad was connected, but our active gamepad is not changing
		return
	end

	if bestGamepad == NONE then
		-- There should be an active gamepad when GamepadConnected fires, so this should not
		-- normally be hit. If there is no active gamepad, unbind actions but leave
		-- the module enabled and continue to listen for a new gamepad connection.
		warn("Gamepad:OnNewGamepadConnected found no connected gamepads")
		self:UnbindContextActions()
		return
	end

	if self.activeGamepad ~= NONE then
		-- Switching from one active gamepad to another
		self:UnbindContextActions()
	end

	self.activeGamepad = bestGamepad
	self:BindContextActions()
end

function Gamepad:OnCurrentGamepadDisconnected()
	if self.activeGamepad ~= NONE then
		ContextActionService:UnbindActivate(self.activeGamepad, Enum.KeyCode.ButtonR2)
	end

	local bestGamepad = self:GetHighestPriorityGamepad()

	if self.activeGamepad ~= NONE and bestGamepad == self.activeGamepad then
		warn("Gamepad:OnCurrentGamepadDisconnected found the supposedly disconnected gamepad in connectedGamepads.")
		self:UnbindContextActions()
		self.activeGamepad = NONE
		return
	end

	if bestGamepad == NONE then
		-- No active gamepad, unbinding actions but leaving gamepad connection listener active
		self:UnbindContextActions()
		self.activeGamepad = NONE
	else
		-- Set new gamepad as active and bind to tool activation
		self.activeGamepad = bestGamepad
		ContextActionService:BindActivate(self.activeGamepad, Enum.KeyCode.ButtonR2)
	end
end

function Gamepad:ConnectGamepadConnectionListeners()
	self.gamepadConnectedConn = UserInputService.GamepadConnected:Connect(function(gamepadEnum)
		self:OnNewGamepadConnected()
	end)

	self.gamepadDisconnectedConn = UserInputService.GamepadDisconnected:Connect(function(gamepadEnum)
		if self.activeGamepad == gamepadEnum then
			self:OnCurrentGamepadDisconnected()
		end
	end)

end

function Gamepad:DisconnectGamepadConnectionListeners()
	if self.gamepadConnectedConn then
		self.gamepadConnectedConn:Disconnect()
		self.gamepadConnectedConn = nil
	end

	if self.gamepadDisconnectedConn then
		self.gamepadDisconnectedConn:Disconnect()
		self.gamepadDisconnectedConn = nil
	end
end

return Gamepad


--!nonstrict
--[[
	Keyboard Character Control - This module handles controlling your avatar from a keyboard

	2018 PlayerScripts Update - AllYourBlox
--]]

--[[ Roblox Services ]]--
local UserInputService = game:GetService("UserInputService")
local ContextActionService = game:GetService("ContextActionService")

--[[ Constants ]]--
local ZERO_VECTOR3 = Vector3.new(0,0,0)

--[[ The Module ]]--
local BaseCharacterController = require(script.Parent:WaitForChild("BaseCharacterController"))
local Keyboard = setmetatable({}, BaseCharacterController)
Keyboard.__index = Keyboard

function Keyboard.new(CONTROL_ACTION_PRIORITY)
	local self = setmetatable(BaseCharacterController.new() :: any, Keyboard)

	self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY

	self.textFocusReleasedConn = nil
	self.textFocusGainedConn = nil
	self.windowFocusReleasedConn = nil

	self.forwardValue  = 0
	self.backwardValue = 0
	self.leftValue = 0
	self.rightValue = 0

	self.jumpEnabled = true

	return self
end

function Keyboard:Enable(enable: boolean)
	if not UserInputService.KeyboardEnabled then
		return false
	end

	if enable == self.enabled then
		-- Module is already in the state being requested. True is returned here since the module will be in the state
		-- expected by the code that follows the Enable() call. This makes more sense than returning false to indicate
		-- no action was necessary. False indicates failure to be in requested/expected state.
		return true
	end

	self.forwardValue  = 0
	self.backwardValue = 0
	self.leftValue = 0
	self.rightValue = 0
	self.moveVector = ZERO_VECTOR3
	self.jumpRequested = false
	self:UpdateJump()

	if enable then
		self:BindContextActions()
		self:ConnectFocusEventListeners()
	else
		self:UnbindContextActions()
		self:DisconnectFocusEventListeners()
	end

	self.enabled = enable
	return true
end

function Keyboard:UpdateMovement(inputState)
	if inputState == Enum.UserInputState.Cancel then
		self.moveVector = ZERO_VECTOR3
	else
		self.moveVector = Vector3.new(self.leftValue + self.rightValue, 0, self.forwardValue + self.backwardValue)
	end
end

function Keyboard:UpdateJump()
	self.isJumping = self.jumpRequested
end

function Keyboard:BindContextActions()

	-- Note: In the previous version of this code, the movement values were not zeroed-out on UserInputState. Cancel, now they are,
	-- which fixes them from getting stuck on.
	-- We return ContextActionResult.Pass here for legacy reasons.
	-- Many games rely on gameProcessedEvent being false on UserInputService.InputBegan for these control actions.
	local handleMoveForward = function(actionName, inputState, inputObject)
		self.forwardValue = (inputState == Enum.UserInputState.Begin) and -1 or 0
		self:UpdateMovement(inputState)
		return Enum.ContextActionResult.Pass
	end

	local handleMoveBackward = function(actionName, inputState, inputObject)
		self.backwardValue = (inputState == Enum.UserInputState.Begin) and 1 or 0
		self:UpdateMovement(inputState)
		return Enum.ContextActionResult.Pass
	end

	local handleMoveLeft = function(actionName, inputState, inputObject)
		self.leftValue = (inputState == Enum.UserInputState.Begin) and -1 or 0
		self:UpdateMovement(inputState)
		return Enum.ContextActionResult.Pass
	end

	local handleMoveRight = function(actionName, inputState, inputObject)
		self.rightValue = (inputState == Enum.UserInputState.Begin) and 1 or 0
		self:UpdateMovement(inputState)
		return Enum.ContextActionResult.Pass
	end

	local handleJumpAction = function(actionName, inputState, inputObject)
		self.jumpRequested = self.jumpEnabled and (inputState == Enum.UserInputState.Begin)
		self:UpdateJump()
		return Enum.ContextActionResult.Pass
	end

	-- TODO: Revert to KeyCode bindings so that in the future the abstraction layer from actual keys to
	-- movement direction is done in Lua
	ContextActionService:BindActionAtPriority("moveForwardAction", handleMoveForward, false,
		self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterForward)
	ContextActionService:BindActionAtPriority("moveBackwardAction", handleMoveBackward, false,
		self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterBackward)
	ContextActionService:BindActionAtPriority("moveLeftAction", handleMoveLeft, false,
		self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterLeft)
	ContextActionService:BindActionAtPriority("moveRightAction", handleMoveRight, false,
		self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterRight)
	ContextActionService:BindActionAtPriority("jumpAction", handleJumpAction, false,
		self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterJump)
end

function Keyboard:UnbindContextActions()
	ContextActionService:UnbindAction("moveForwardAction")
	ContextActionService:UnbindAction("moveBackwardAction")
	ContextActionService:UnbindAction("moveLeftAction")
	ContextActionService:UnbindAction("moveRightAction")
	ContextActionService:UnbindAction("jumpAction")
end

function Keyboard:ConnectFocusEventListeners()
	local function onFocusReleased()
		self.moveVector = ZERO_VECTOR3
		self.forwardValue  = 0
		self.backwardValue = 0
		self.leftValue = 0
		self.rightValue = 0
		self.jumpRequested = false
		self:UpdateJump()
	end

	local function onTextFocusGained(textboxFocused)
		self.jumpRequested = false
		self:UpdateJump()
	end

	self.textFocusReleasedConn = UserInputService.TextBoxFocusReleased:Connect(onFocusReleased)
	self.textFocusGainedConn = UserInputService.TextBoxFocused:Connect(onTextFocusGained)
	self.windowFocusReleasedConn = UserInputService.WindowFocused:Connect(onFocusReleased)
end

function Keyboard:DisconnectFocusEventListeners()
	if self.textFocusReleasedConn then
		self.textFocusReleasedConn:Disconnect()
		self.textFocusReleasedConn = nil
	end
	if self.textFocusGainedConn then
		self.textFocusGainedConn:Disconnect()
		self.textFocusGainedConn = nil
	end
	if self.windowFocusReleasedConn then
		self.windowFocusReleasedConn:Disconnect()
		self.windowFocusReleasedConn = nil
	end
end

return Keyboard


--!nonstrict
local Players = game:GetService("Players")

local PathDisplay = {}
PathDisplay.spacing = 8
PathDisplay.image = "rbxasset://textures/Cursors/Gamepad/Pointer.png"
PathDisplay.imageSize = Vector2.new(2, 2)

local currentPoints = {}
local renderedPoints = {}

local pointModel = Instance.new("Model")
pointModel.Name = "PathDisplayPoints"

local adorneePart = Instance.new("Part")
adorneePart.Anchored = true
adorneePart.CanCollide = false
adorneePart.Transparency = 1
adorneePart.Name = "PathDisplayAdornee"
adorneePart.CFrame = CFrame.new(0, 0, 0)
adorneePart.Parent = pointModel

local pointPool = {}
local poolTop = 30
for i = 1, poolTop do
	local point = Instance.new("ImageHandleAdornment")
	point.Archivable = false
	point.Adornee = adorneePart
	point.Image = PathDisplay.image
	point.Size = PathDisplay.imageSize
	pointPool[i] = point
end

local function retrieveFromPool(): ImageHandleAdornment?
	local point = pointPool[1]
	if not point then
		return nil
	end

	pointPool[1], pointPool[poolTop] = pointPool[poolTop], nil
	poolTop = poolTop - 1
	return point
end

local function returnToPool(point: ImageHandleAdornment)
	poolTop = poolTop + 1
	pointPool[poolTop] = point
end

local function renderPoint(point: Vector3, isLast): ImageHandleAdornment?
	if poolTop == 0 then
		return nil
	end

	local rayDown = Ray.new(point + Vector3.new(0, 2, 0), Vector3.new(0, -8, 0))
	local hitPart, hitPoint, hitNormal = workspace:FindPartOnRayWithIgnoreList(rayDown, { (Players.LocalPlayer :: Player).Character :: Model, workspace.CurrentCamera :: Camera })
	if not hitPart then
		return nil
	end

	local pointCFrame = CFrame.new(hitPoint, hitPoint + hitNormal)

	local Point = retrieveFromPool()
	Point.CFrame = pointCFrame
	Point.Parent = pointModel
	return Point
end

function PathDisplay.setCurrentPoints(points)
	if typeof(points) == 'table' then
		currentPoints = points
	else
		currentPoints = {}
	end
end

function PathDisplay.clearRenderedPath()
	for _, oldPoint in ipairs(renderedPoints) do
		oldPoint.Parent = nil
		returnToPool(oldPoint)
	end
	renderedPoints = {}
	pointModel.Parent = nil
end

function PathDisplay.renderPath()
	PathDisplay.clearRenderedPath()
	if not currentPoints or #currentPoints == 0 then
		return
	end

	local currentIdx = #currentPoints
	local lastPos = currentPoints[currentIdx]
	local distanceBudget = 0

	renderedPoints[1] = renderPoint(lastPos, true)
	if not renderedPoints[1] then
		return
	end

	while true do
		local currentPoint = currentPoints[currentIdx]
		local nextPoint = currentPoints[currentIdx - 1]

		if currentIdx < 2 then
			break
		else

			local toNextPoint = nextPoint - currentPoint
			local distToNextPoint = toNextPoint.magnitude

			if distanceBudget > distToNextPoint then
				distanceBudget = distanceBudget - distToNextPoint
				currentIdx = currentIdx - 1
			else
				local dirToNextPoint = toNextPoint.unit
				local pointPos = currentPoint + (dirToNextPoint * distanceBudget)
				local point = renderPoint(pointPos, false)

				if point then
					renderedPoints[#renderedPoints + 1] = point
				end

				distanceBudget = distanceBudget + PathDisplay.spacing
			end
		end
	end

	pointModel.Parent = workspace.CurrentCamera
end

return PathDisplay


--!nonstrict
--[[
	// FileName: TouchJump
	// Version 1.0
	// Written by: jmargh
	// Description: Implements jump controls for touch devices. Use with Thumbstick and Thumbpad
--]]

local Players = game:GetService("Players")
local GuiService = game:GetService("GuiService")

--[[ Constants ]]--
local TOUCH_CONTROL_SHEET = "rbxasset://textures/ui/Input/TouchControlsSheetV2.png"

--[[ The Module ]]--
local BaseCharacterController = require(script.Parent:WaitForChild("BaseCharacterController"))
local TouchJump = setmetatable({}, BaseCharacterController)
TouchJump.__index = TouchJump

function TouchJump.new()
	local self = setmetatable(BaseCharacterController.new() :: any, TouchJump)

	self.parentUIFrame = nil
	self.jumpButton = nil
	self.characterAddedConn = nil
	self.humanoidStateEnabledChangedConn = nil
	self.humanoidJumpPowerConn = nil
	self.humanoidParentConn = nil
	self.externallyEnabled = false
	self.jumpPower = 0
	self.jumpStateEnabled = true
	self.isJumping = false
	self.humanoid = nil -- saved reference because property change connections are made using it

	return self
end

function TouchJump:EnableButton(enable)
	if enable then
		if not self.jumpButton then
			self:Create()
		end
		local humanoid = Players.LocalPlayer.Character and Players.LocalPlayer.Character:FindFirstChildWhichIsA("Humanoid")
		if humanoid and self.externallyEnabled then
			if self.externallyEnabled then
				if humanoid.JumpPower > 0 then
					self.jumpButton.Visible = true
				end
			end
		end
	else
		self.jumpButton.Visible = false
		self.isJumping = false
		self.jumpButton.ImageRectOffset = Vector2.new(1, 146)
	end
end

function TouchJump:UpdateEnabled()
	if self.jumpPower > 0 and self.jumpStateEnabled then
		self:EnableButton(true)
	else
		self:EnableButton(false)
	end
end

function TouchJump:HumanoidChanged(prop)
	local humanoid = Players.LocalPlayer.Character and Players.LocalPlayer.Character:FindFirstChildWhichIsA("Humanoid")
	if humanoid then
		if prop == "JumpPower" then
			self.jumpPower =  humanoid.JumpPower
			self:UpdateEnabled()
		elseif prop == "Parent" then
			if not humanoid.Parent then
				self.humanoidChangeConn:Disconnect()
			end
		end
	end
end

function TouchJump:HumanoidStateEnabledChanged(state, isEnabled)
	if state == Enum.HumanoidStateType.Jumping then
		self.jumpStateEnabled = isEnabled
		self:UpdateEnabled()
	end
end

function TouchJump:CharacterAdded(char)
	if self.humanoidChangeConn then
		self.humanoidChangeConn:Disconnect()
		self.humanoidChangeConn = nil
	end

	self.humanoid = char:FindFirstChildWhichIsA("Humanoid")
	while not self.humanoid do
		char.ChildAdded:wait()
		self.humanoid = char:FindFirstChildWhichIsA("Humanoid")
	end

	self.humanoidJumpPowerConn = self.humanoid:GetPropertyChangedSignal("JumpPower"):Connect(function()
		self.jumpPower =  self.humanoid.JumpPower
		self:UpdateEnabled()
	end)

	self.humanoidParentConn = self.humanoid:GetPropertyChangedSignal("Parent"):Connect(function()
		if not self.humanoid.Parent then
			self.humanoidJumpPowerConn:Disconnect()
			self.humanoidJumpPowerConn = nil
			self.humanoidParentConn:Disconnect()
			self.humanoidParentConn = nil
		end
	end)

	self.humanoidStateEnabledChangedConn = self.humanoid.StateEnabledChanged:Connect(function(state, enabled)
		self:HumanoidStateEnabledChanged(state, enabled)
	end)

	self.jumpPower = self.humanoid.JumpPower
	self.jumpStateEnabled = self.humanoid:GetStateEnabled(Enum.HumanoidStateType.Jumping)
	self:UpdateEnabled()
end

function TouchJump:SetupCharacterAddedFunction()
	self.characterAddedConn = Players.LocalPlayer.CharacterAdded:Connect(function(char)
		self:CharacterAdded(char)
	end)
	if Players.LocalPlayer.Character then
		self:CharacterAdded(Players.LocalPlayer.Character)
	end
end

function TouchJump:Enable(enable, parentFrame)
	if parentFrame then
		self.parentUIFrame = parentFrame
	end
	self.externallyEnabled = enable
	self:EnableButton(enable)
end

function TouchJump:Create()
	if not self.parentUIFrame then
		return
	end

	if self.jumpButton then
		self.jumpButton:Destroy()
		self.jumpButton = nil
	end

	local minAxis = math.min(self.parentUIFrame.AbsoluteSize.x, self.parentUIFrame.AbsoluteSize.y)
	local isSmallScreen = minAxis <= 500
	local jumpButtonSize = isSmallScreen and 70 or 120

	self.jumpButton = Instance.new("ImageButton")
	self.jumpButton.Name = "JumpButton"
	self.jumpButton.Visible = false
	self.jumpButton.BackgroundTransparency = 1
	self.jumpButton.Image = TOUCH_CONTROL_SHEET
	self.jumpButton.ImageRectOffset = Vector2.new(1, 146)
	self.jumpButton.ImageRectSize = Vector2.new(144, 144)
	self.jumpButton.Size = UDim2.new(0, jumpButtonSize, 0, jumpButtonSize)

    self.jumpButton.Position = isSmallScreen and UDim2.new(1, -(jumpButtonSize*1.5-10), 1, -jumpButtonSize - 20) or
        UDim2.new(1, -(jumpButtonSize*1.5-10), 1, -jumpButtonSize * 1.75)

	local touchObject: InputObject? = nil
	self.jumpButton.InputBegan:connect(function(inputObject)
		--A touch that starts elsewhere on the screen will be sent to a frame's InputBegan event
		--if it moves over the frame. So we check that this is actually a new touch (inputObject.UserInputState ~= Enum.UserInputState.Begin)
		if touchObject or inputObject.UserInputType ~= Enum.UserInputType.Touch
			or inputObject.UserInputState ~= Enum.UserInputState.Begin then
			return
		end

		touchObject = inputObject
		self.jumpButton.ImageRectOffset = Vector2.new(146, 146)
		self.isJumping = true
	end)

	local OnInputEnded = function()
		touchObject = nil
		self.isJumping = false
		self.jumpButton.ImageRectOffset = Vector2.new(1, 146)
	end

	self.jumpButton.InputEnded:connect(function(inputObject: InputObject)
		if inputObject == touchObject then
			OnInputEnded()
		end
	end)

	GuiService.MenuOpened:connect(function()
		if touchObject then
			OnInputEnded()
		end
	end)

	if not self.characterAddedConn then
		self:SetupCharacterAddedFunction()
	end

	self.jumpButton.Parent = self.parentUIFrame
end

return TouchJump


--!nonstrict
--[[

	TouchThumbstick

--]]
local GuiService = game:GetService("GuiService")
local UserInputService = game:GetService("UserInputService")

local FFlagUserClampClassicThumbstick do
	local success, result = pcall(function()
		return UserSettings():IsUserFeatureEnabled("UserClampClassicThumbstick")
	end)
	FFlagUserClampClassicThumbstick = success and result
end

--[[ Constants ]]--
local ZERO_VECTOR3 = Vector3.new(0,0,0)
local TOUCH_CONTROL_SHEET = "rbxasset://textures/ui/TouchControlsSheet.png"
--[[ The Module ]]--
local BaseCharacterController = require(script.Parent:WaitForChild("BaseCharacterController"))
local TouchThumbstick = setmetatable({}, BaseCharacterController)
TouchThumbstick.__index = TouchThumbstick
function TouchThumbstick.new()
	local self = setmetatable(BaseCharacterController.new() :: any, TouchThumbstick)

	self.isFollowStick = false

	self.thumbstickFrame = nil
	self.moveTouchObject = nil
	self.onTouchMovedConn = nil
	self.onTouchEndedConn = nil
	self.screenPos = nil
	self.stickImage = nil
	self.thumbstickSize = nil -- Float

	return self
end
function TouchThumbstick:Enable(enable: boolean?, uiParentFrame)
	if enable == nil then return false end			-- If nil, return false (invalid argument)
	enable = enable and true or false				-- Force anything non-nil to boolean before comparison
	if self.enabled == enable then
		return true
	end	-- If no state change, return true indicating already in requested state

	self.moveVector = ZERO_VECTOR3
	self.isJumping = false

	if enable then
		-- Enable
		if not self.thumbstickFrame then
			self:Create(uiParentFrame)
		end
		self.thumbstickFrame.Visible = true
	else
		-- Disable
		self.thumbstickFrame.Visible = false
		self:OnInputEnded()
	end
	self.enabled = enable

	return
end
function TouchThumbstick:OnInputEnded()
	self.thumbstickFrame.Position = self.screenPos
	self.stickImage.Position = UDim2.new(0, self.thumbstickFrame.Size.X.Offset/2 - self.thumbstickSize/4, 0, self.thumbstickFrame.Size.Y.Offset/2 - self.thumbstickSize/4)

	self.moveVector = ZERO_VECTOR3
	self.isJumping = false
	self.thumbstickFrame.Position = self.screenPos
	self.moveTouchObject = nil
end
function TouchThumbstick:Create(parentFrame)

	if self.thumbstickFrame then
		self.thumbstickFrame:Destroy()
		self.thumbstickFrame = nil
		if self.onTouchMovedConn then
			self.onTouchMovedConn:Disconnect()
			self.onTouchMovedConn = nil
		end
		if self.onTouchEndedConn then
			self.onTouchEndedConn:Disconnect()
			self.onTouchEndedConn = nil
		end
	end

	local minAxis = math.min(parentFrame.AbsoluteSize.X, parentFrame.AbsoluteSize.Y)
	local isSmallScreen = minAxis <= 500
	self.thumbstickSize = isSmallScreen and 70 or 120
	self.screenPos = isSmallScreen and UDim2.new(0, (self.thumbstickSize/2) - 10, 1, -self.thumbstickSize - 20) or
		UDim2.new(0, self.thumbstickSize/2, 1, -self.thumbstickSize * 1.75)

	self.thumbstickFrame = Instance.new("Frame")
	self.thumbstickFrame.Name = "ThumbstickFrame"
	self.thumbstickFrame.Active = true
	self.thumbstickFrame.Visible = false
	self.thumbstickFrame.Size = UDim2.new(0, self.thumbstickSize, 0, self.thumbstickSize)
	self.thumbstickFrame.Position = self.screenPos
	self.thumbstickFrame.BackgroundTransparency = 1

	local outerImage = Instance.new("ImageLabel")
	outerImage.Name = "OuterImage"
	outerImage.Image = TOUCH_CONTROL_SHEET
	outerImage.ImageRectOffset = Vector2.new()
	outerImage.ImageRectSize = Vector2.new(220, 220)
	outerImage.BackgroundTransparency = 1
	outerImage.Size = UDim2.new(0, self.thumbstickSize, 0, self.thumbstickSize)
	outerImage.Position = UDim2.new(0, 0, 0, 0)
	outerImage.Parent = self.thumbstickFrame

	self.stickImage = Instance.new("ImageLabel")
	self.stickImage.Name = "StickImage"
	self.stickImage.Image = TOUCH_CONTROL_SHEET
	self.stickImage.ImageRectOffset = Vector2.new(220, 0)
	self.stickImage.ImageRectSize = Vector2.new(111, 111)
	self.stickImage.BackgroundTransparency = 1
	self.stickImage.Size = UDim2.new(0, self.thumbstickSize/2, 0, self.thumbstickSize/2)
	self.stickImage.Position = UDim2.new(0, self.thumbstickSize/2 - self.thumbstickSize/4, 0, self.thumbstickSize/2 - self.thumbstickSize/4)
	self.stickImage.ZIndex = 2
	self.stickImage.Parent = self.thumbstickFrame

	local centerPosition = nil
	local deadZone = 0.05

	local function DoMove(direction: Vector2)

		local currentMoveVector = direction / (self.thumbstickSize/2)

		-- Scaled Radial Dead Zone
		local inputAxisMagnitude = currentMoveVector.magnitude
		if inputAxisMagnitude < deadZone then
			currentMoveVector = Vector3.new()
		else
			if FFlagUserClampClassicThumbstick then
				currentMoveVector = currentMoveVector.unit * math.min(1, (inputAxisMagnitude - deadZone) / (1 - deadZone))
			else
				currentMoveVector = currentMoveVector.unit * ((inputAxisMagnitude - deadZone) / (1 - deadZone))
			end
			-- NOTE: Making currentMoveVector a unit vector will cause the player to instantly go max speed
			-- must check for zero length vector is using unit
			currentMoveVector = Vector3.new(currentMoveVector.X, 0, currentMoveVector.Y)
		end

		self.moveVector = currentMoveVector
	end

	local function MoveStick(pos: Vector3)
		local relativePosition = Vector2.new(pos.X - centerPosition.X, pos.Y - centerPosition.Y)
		local length = relativePosition.magnitude
		local maxLength = self.thumbstickFrame.AbsoluteSize.X/2
		if self.isFollowStick and length > maxLength then
			local offset = relativePosition.unit * maxLength
			self.thumbstickFrame.Position = UDim2.new(
				0, pos.X - self.thumbstickFrame.AbsoluteSize.X/2 - offset.X,
				0, pos.Y - self.thumbstickFrame.AbsoluteSize.Y/2 - offset.Y)
		else
			length = math.min(length, maxLength)
			relativePosition = relativePosition.unit * length
		end
		self.stickImage.Position = UDim2.new(0, relativePosition.X + self.stickImage.AbsoluteSize.X/2, 0, relativePosition.Y + self.stickImage.AbsoluteSize.Y/2)
	end

	-- input connections
	self.thumbstickFrame.InputBegan:Connect(function(inputObject: InputObject)
		--A touch that starts elsewhere on the screen will be sent to a frame's InputBegan event
		--if it moves over the frame. So we check that this is actually a new touch (inputObject.UserInputState ~= Enum.UserInputState.Begin)
		if self.moveTouchObject or inputObject.UserInputType ~= Enum.UserInputType.Touch
			or inputObject.UserInputState ~= Enum.UserInputState.Begin then
			return
		end

		self.moveTouchObject = inputObject
		self.thumbstickFrame.Position = UDim2.new(0, inputObject.Position.X - self.thumbstickFrame.Size.X.Offset/2, 0, inputObject.Position.Y - self.thumbstickFrame.Size.Y.Offset/2)
		centerPosition = Vector2.new(self.thumbstickFrame.AbsolutePosition.X + self.thumbstickFrame.AbsoluteSize.X/2,
			self.thumbstickFrame.AbsolutePosition.Y + self.thumbstickFrame.AbsoluteSize.Y/2)
	end)

	self.onTouchMovedConn = UserInputService.TouchMoved:Connect(function(inputObject: InputObject, isProcessed: boolean)
		if inputObject == self.moveTouchObject then
			centerPosition = Vector2.new(self.thumbstickFrame.AbsolutePosition.X + self.thumbstickFrame.AbsoluteSize.X/2,
				self.thumbstickFrame.AbsolutePosition.Y + self.thumbstickFrame.AbsoluteSize.Y/2)
			local direction = Vector2.new(inputObject.Position.X - centerPosition.X, inputObject.Position.Y - centerPosition.Y)
			DoMove(direction)
			MoveStick(inputObject.Position)
		end
	end)

	self.onTouchEndedConn = UserInputService.TouchEnded:Connect(function(inputObject, isProcessed)
		if inputObject == self.moveTouchObject then
			self:OnInputEnded()
		end
	end)

	GuiService.MenuOpened:Connect(function()
		if self.moveTouchObject then
			self:OnInputEnded()
		end
	end)

	self.thumbstickFrame.Parent = parentFrame
end
return TouchThumbstick


--!nonstrict
--!nolint GlobalUsedAsLocal

--[[
		VRNavigation
--]]

local VRService = game:GetService("VRService")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local Players = game:GetService("Players")
local PathfindingService = game:GetService("PathfindingService")
local ContextActionService = game:GetService("ContextActionService")
local StarterGui = game:GetService("StarterGui")

--local MasterControl = require(script.Parent)
local PathDisplay = nil
local LocalPlayer = Players.LocalPlayer

--[[ Constants ]]--
local RECALCULATE_PATH_THRESHOLD = 4
local NO_PATH_THRESHOLD = 12
local MAX_PATHING_DISTANCE = 200
local POINT_REACHED_THRESHOLD = 1
local OFFTRACK_TIME_THRESHOLD = 2
local THUMBSTICK_DEADZONE = 0.22

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

--[[ Utility Functions ]]--
local function IsFinite(num: number)
	return num == num and num ~= 1/0 and num ~= -1/0
end

local function IsFiniteVector3(vec3)
	return IsFinite(vec3.x) and IsFinite(vec3.y) and IsFinite(vec3.z)
end

local movementUpdateEvent = Instance.new("BindableEvent")
movementUpdateEvent.Name = "MovementUpdate"
movementUpdateEvent.Parent = script

coroutine.wrap(function()
	local PathDisplayModule = script.Parent:WaitForChild("PathDisplay")
	if PathDisplayModule then
		PathDisplay = require(PathDisplayModule)
	end
end)()

--[[ The Class ]]--
local BaseCharacterController = require(script.Parent:WaitForChild("BaseCharacterController"))
local VRNavigation = setmetatable({}, BaseCharacterController)
VRNavigation.__index = VRNavigation

function VRNavigation.new(CONTROL_ACTION_PRIORITY)
	local self = setmetatable(BaseCharacterController.new() :: any, VRNavigation)

	self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY

	self.navigationRequestedConn = nil
	self.heartbeatConn = nil

	self.currentDestination = nil
	self.currentPath = nil
	self.currentPoints = nil
	self.currentPointIdx = 0

	self.expectedTimeToNextPoint = 0
	self.timeReachedLastPoint = tick()
	self.moving = false

	self.isJumpBound = false
	self.moveLatch = false

	self.userCFrameEnabledConn = nil

	return self
end

function VRNavigation:SetLaserPointerMode(mode)
	pcall(function()
		StarterGui:SetCore("VRLaserPointerMode", mode)
	end)
end

function VRNavigation:GetLocalHumanoid()
	local character = LocalPlayer.Character
	if not character then
		return
	end

	for _, child in pairs(character:GetChildren()) do
		if child:IsA("Humanoid") then
			return child
		end
	end
	return nil
end

function VRNavigation:HasBothHandControllers()
	return VRService:GetUserCFrameEnabled(Enum.UserCFrame.RightHand) and VRService:GetUserCFrameEnabled(Enum.UserCFrame.LeftHand)
end

function VRNavigation:HasAnyHandControllers()
	return VRService:GetUserCFrameEnabled(Enum.UserCFrame.RightHand) or VRService:GetUserCFrameEnabled(Enum.UserCFrame.LeftHand)
end

function VRNavigation:IsMobileVR()
	return UserInputService.TouchEnabled
end

function VRNavigation:HasGamepad()
	return UserInputService.GamepadEnabled
end

function VRNavigation:ShouldUseNavigationLaser()
	--Places where we use the navigation laser:
	-- mobile VR with any number of hands tracked
	-- desktop VR with only one hand tracked
	-- desktop VR with no hands and no gamepad (i.e. with Oculus remote?)
	--using an Xbox controller with a desktop VR headset means no laser since the user has a thumbstick.
	--in the future, we should query thumbstick presence with a features API
	if self:IsMobileVR() then
		return true
	else
		if self:HasBothHandControllers() then
			return false
		end
		if not self:HasAnyHandControllers() then
			return not self:HasGamepad()
		end
		return true
	end
end

function VRNavigation:StartFollowingPath(newPath)
	currentPath = newPath
	currentPoints = currentPath:GetPointCoordinates()
	currentPointIdx = 1
	moving = true

	timeReachedLastPoint = tick()

	local humanoid = self:GetLocalHumanoid()
	if humanoid and humanoid.Torso and #currentPoints >= 1 then
		local dist = (currentPoints[1] - humanoid.Torso.Position).magnitude
		expectedTimeToNextPoint = dist / humanoid.WalkSpeed
	end

	movementUpdateEvent:Fire("targetPoint", self.currentDestination)
end

function VRNavigation:GoToPoint(point)
	currentPath = true
	currentPoints = { point }
	currentPointIdx = 1
	moving = true

	local humanoid = self:GetLocalHumanoid()
	local distance = (humanoid.Torso.Position - point).magnitude
	local estimatedTimeRemaining = distance / humanoid.WalkSpeed

	timeReachedLastPoint = tick()
	expectedTimeToNextPoint = estimatedTimeRemaining

	movementUpdateEvent:Fire("targetPoint", point)
end

function VRNavigation:StopFollowingPath()
	currentPath = nil
	currentPoints = nil
	currentPointIdx = 0
	moving = false
	self.moveVector = ZERO_VECTOR3
end

function VRNavigation:TryComputePath(startPos: Vector3, destination: Vector3)
	local numAttempts = 0
	local newPath = nil

	while not newPath and numAttempts < 5 do
		newPath = PathfindingService:ComputeSmoothPathAsync(startPos, destination, MAX_PATHING_DISTANCE)
		numAttempts = numAttempts + 1

		if newPath.Status == Enum.PathStatus.ClosestNoPath or newPath.Status == Enum.PathStatus.ClosestOutOfRange then
			newPath = nil
			break
		end

		if newPath and newPath.Status == Enum.PathStatus.FailStartNotEmpty then
			startPos = startPos + (destination - startPos).Unit
			newPath = nil
		end

		if newPath and newPath.Status == Enum.PathStatus.FailFinishNotEmpty then
			destination = destination + Vector3.new(0, 1, 0)
			newPath = nil
		end
	end

	return newPath
end

function VRNavigation:OnNavigationRequest(destinationCFrame: CFrame, inputUserCFrame: CFrame)
	local destinationPosition = destinationCFrame.Position
	local lastDestination = self.currentDestination

	if not IsFiniteVector3(destinationPosition) then
		return
	end

	self.currentDestination = destinationPosition

	local humanoid = self:GetLocalHumanoid()
	if not humanoid or not humanoid.Torso then
		return
	end

	local currentPosition = humanoid.Torso.Position
	local distanceToDestination = (self.currentDestination - currentPosition).magnitude

	if distanceToDestination < NO_PATH_THRESHOLD then
		self:GoToPoint(self.currentDestination)
		return
	end

	if not lastDestination or (self.currentDestination - lastDestination).magnitude > RECALCULATE_PATH_THRESHOLD then
		local newPath = self:TryComputePath(currentPosition, self.currentDestination)
		if newPath then
			self:StartFollowingPath(newPath)
			if PathDisplay then
				PathDisplay.setCurrentPoints(self.currentPoints)
				PathDisplay.renderPath()
			end
		else
			self:StopFollowingPath()
			if PathDisplay then
				PathDisplay.clearRenderedPath()
			end
		end
	else
		if moving then
			self.currentPoints[#currentPoints] = self.currentDestination
		else
			self:GoToPoint(self.currentDestination)
		end
	end
end

function VRNavigation:OnJumpAction(actionName, inputState, inputObj)
	if inputState == Enum.UserInputState.Begin then
		self.isJumping = true
	end
	return Enum.ContextActionResult.Sink
end
function VRNavigation:BindJumpAction(active)
	if active then
		if not self.isJumpBound then
			self.isJumpBound = true
			ContextActionService:BindActionAtPriority("VRJumpAction", (function() return self:OnJumpAction() end), false,
				self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.ButtonA)
		end
	else
		if self.isJumpBound then
			self.isJumpBound = false
			ContextActionService:UnbindAction("VRJumpAction")
		end
	end
end

function VRNavigation:ControlCharacterGamepad(actionName, inputState, inputObject)
	if inputObject.KeyCode ~= Enum.KeyCode.Thumbstick1 then return end

	if inputState == Enum.UserInputState.Cancel then
		self.moveVector =  ZERO_VECTOR3
		return
	end

	if inputState ~= Enum.UserInputState.End then
		self:StopFollowingPath()
		if PathDisplay then
			PathDisplay.clearRenderedPath()
		end

		if self:ShouldUseNavigationLaser() then
			self:BindJumpAction(true)
			self:SetLaserPointerMode("Hidden")
		end

		if inputObject.Position.magnitude > THUMBSTICK_DEADZONE then
			self.moveVector = Vector3.new(inputObject.Position.X, 0, -inputObject.Position.Y)
			if self.moveVector.magnitude > 0 then
				self.moveVector = self.moveVector.unit * math.min(1, inputObject.Position.magnitude)
			end

			self.moveLatch = true
		end
	else
		self.moveVector =  ZERO_VECTOR3

		if self:ShouldUseNavigationLaser() then
			self:BindJumpAction(false)
			self:SetLaserPointerMode("Navigation")
		end

		if self.moveLatch then
			self.moveLatch = false
			movementUpdateEvent:Fire("offtrack")
		end
	end
	return Enum.ContextActionResult.Sink
end

function VRNavigation:OnHeartbeat(dt)
	local newMoveVector = self.moveVector
	local humanoid = self:GetLocalHumanoid()
	if not humanoid or not humanoid.Torso then
		return
	end

	if self.moving and self.currentPoints then
		local currentPosition = humanoid.Torso.Position
		local goalPosition = currentPoints[1]
		local vectorToGoal = (goalPosition - currentPosition) * XZ_VECTOR3
		local moveDist = vectorToGoal.magnitude
		local moveDir = vectorToGoal / moveDist

		if moveDist < POINT_REACHED_THRESHOLD then
			local estimatedTimeRemaining = 0
			local prevPoint = currentPoints[1]
			for i, point in pairs(currentPoints) do
				if i ~= 1 then
					local dist = (point - prevPoint).magnitude
					prevPoint = point
					estimatedTimeRemaining = estimatedTimeRemaining + (dist / humanoid.WalkSpeed)
				end
			end

			table.remove(currentPoints, 1)
			currentPointIdx = currentPointIdx + 1

			if #currentPoints == 0 then
				self:StopFollowingPath()
				if PathDisplay then
					PathDisplay.clearRenderedPath()
				end
				return
			else
				if PathDisplay then
					PathDisplay.setCurrentPoints(currentPoints)
					PathDisplay.renderPath()
				end

				local newGoal = currentPoints[1]
				local distanceToGoal = (newGoal - currentPosition).magnitude
				expectedTimeToNextPoint = distanceToGoal / humanoid.WalkSpeed
				timeReachedLastPoint = tick()
			end
		else
			local ignoreTable = {
				(game.Players.LocalPlayer :: Player).Character,
				workspace.CurrentCamera
			}
			local obstructRay = Ray.new(currentPosition - Vector3.new(0, 1, 0), moveDir * 3)
			local obstructPart, obstructPoint, _obstructNormal = workspace:FindPartOnRayWithIgnoreList(obstructRay, ignoreTable)

			if obstructPart then
				local heightOffset = Vector3.new(0, 100, 0)
				local jumpCheckRay = Ray.new(obstructPoint + moveDir * 0.5 + heightOffset, -heightOffset)
				local _jumpCheckPart, jumpCheckPoint, _jumpCheckNormal = workspace:FindPartOnRayWithIgnoreList(jumpCheckRay, ignoreTable)

				local heightDifference = jumpCheckPoint.Y - currentPosition.Y
				if heightDifference < 6 and heightDifference > -2 then
					humanoid.Jump = true
				end
			end

			local timeSinceLastPoint = tick() - timeReachedLastPoint
			if timeSinceLastPoint > expectedTimeToNextPoint + OFFTRACK_TIME_THRESHOLD then
				self:StopFollowingPath()
				if PathDisplay then
					PathDisplay.clearRenderedPath()
				end

				movementUpdateEvent:Fire("offtrack")
			end

			newMoveVector = self.moveVector:Lerp(moveDir, dt * 10)
		end
	end

	if IsFiniteVector3(newMoveVector) then
		self.moveVector = newMoveVector
	end
end

function VRNavigation:OnUserCFrameEnabled()
	if self:ShouldUseNavigationLaser() then
		self:BindJumpAction(false)
		self:SetLaserPointerMode("Navigation")
	else
		self:BindJumpAction(true)
		self:SetLaserPointerMode("Hidden")
	end
end

function VRNavigation:Enable(enable)

	self.moveVector = ZERO_VECTOR3
	self.isJumping = false

	if enable then
		self.navigationRequestedConn = VRService.NavigationRequested:Connect(function(destinationCFrame, inputUserCFrame) self:OnNavigationRequest(destinationCFrame, inputUserCFrame) end)
		self.heartbeatConn = RunService.Heartbeat:Connect(function(dt) self:OnHeartbeat(dt) end)

		ContextActionService:BindAction("MoveThumbstick", (function(actionName, inputState, inputObject) return self:ControlCharacterGamepad(actionName, inputState, inputObject) end),
			false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.Thumbstick1)
		ContextActionService:BindActivate(Enum.UserInputType.Gamepad1, Enum.KeyCode.ButtonR2)

		self.userCFrameEnabledConn = VRService.UserCFrameEnabled:Connect(function() self:OnUserCFrameEnabled() end)
		self:OnUserCFrameEnabled()

		VRService:SetTouchpadMode(Enum.VRTouchpad.Left, Enum.VRTouchpadMode.VirtualThumbstick)
		VRService:SetTouchpadMode(Enum.VRTouchpad.Right, Enum.VRTouchpadMode.ABXY)

		self.enabled = true
	else
		-- Disable
		self:StopFollowingPath()

		ContextActionService:UnbindAction("MoveThumbstick")
		ContextActionService:UnbindActivate(Enum.UserInputType.Gamepad1, Enum.KeyCode.ButtonR2)

		self:BindJumpAction(false)
		self:SetLaserPointerMode("Disabled")

		if self.navigationRequestedConn then
			self.navigationRequestedConn:Disconnect()
			self.navigationRequestedConn = nil
		end
		if self.heartbeatConn then
			self.heartbeatConn:Disconnect()
			self.heartbeatConn = nil
		end
		if self.userCFrameEnabledConn then
			self.userCFrameEnabledConn:Disconnect()
			self.userCFrameEnabledConn = nil
		end
		self.enabled = false
	end
end

return VRNavigation


--!nonstrict
--[[
	// FileName: VehicleControl
	// Version 1.0
	// Written by: jmargh
	// Description: Implements in-game vehicle controls for all input devices

	// NOTE: This works for basic vehicles (single vehicle seat). If you use custom VehicleSeat code,
	// multiple VehicleSeats or your own implementation of a VehicleSeat this will not work.
--]]
local ContextActionService = game:GetService("ContextActionService")

--[[ Constants ]]--
-- Set this to true if you want to instead use the triggers for the throttle
local useTriggersForThrottle = true
-- Also set this to true if you want the thumbstick to not affect throttle, only triggers when a gamepad is conected
local onlyTriggersForThrottle = false
local ZERO_VECTOR3 = Vector3.new(0,0,0)

local AUTO_PILOT_DEFAULT_MAX_STEERING_ANGLE = 35

-- Note that VehicleController does not derive from BaseCharacterController, it is a special case
local VehicleController = {}
VehicleController.__index = VehicleController

function VehicleController.new(CONTROL_ACTION_PRIORITY)
	local self = setmetatable({}, VehicleController)

	self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY

	self.enabled = false
	self.vehicleSeat = nil
	self.throttle = 0
	self.steer = 0

	self.acceleration = 0
	self.decceleration = 0
	self.turningRight = 0
	self.turningLeft = 0

	self.vehicleMoveVector = ZERO_VECTOR3

	self.autoPilot = {}
	self.autoPilot.MaxSpeed = 0
	self.autoPilot.MaxSteeringAngle = 0

	return self
end

function VehicleController:BindContextActions()
	if useTriggersForThrottle then
		ContextActionService:BindActionAtPriority("throttleAccel", (function(actionName, inputState, inputObject)
			self:OnThrottleAccel(actionName, inputState, inputObject)
			return Enum.ContextActionResult.Pass
		end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.ButtonR2)
		ContextActionService:BindActionAtPriority("throttleDeccel", (function(actionName, inputState, inputObject)
			self:OnThrottleDeccel(actionName, inputState, inputObject)
			return Enum.ContextActionResult.Pass
		end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.ButtonL2)
	end
	ContextActionService:BindActionAtPriority("arrowSteerRight", (function(actionName, inputState, inputObject)
		self:OnSteerRight(actionName, inputState, inputObject)
		return Enum.ContextActionResult.Pass
	end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.Right)
	ContextActionService:BindActionAtPriority("arrowSteerLeft", (function(actionName, inputState, inputObject)
		self:OnSteerLeft(actionName, inputState, inputObject)
		return Enum.ContextActionResult.Pass
	end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.Left)
end

function VehicleController:Enable(enable: boolean, vehicleSeat: VehicleSeat)
	if enable == self.enabled and vehicleSeat == self.vehicleSeat then
		return
	end

	self.enabled = enable
	self.vehicleMoveVector = ZERO_VECTOR3

	if enable then
		if vehicleSeat then
			self.vehicleSeat = vehicleSeat

			self:SetupAutoPilot()
			self:BindContextActions()
		end
	else
		if useTriggersForThrottle then
			ContextActionService:UnbindAction("throttleAccel")
			ContextActionService:UnbindAction("throttleDeccel")
		end
		ContextActionService:UnbindAction("arrowSteerRight")
		ContextActionService:UnbindAction("arrowSteerLeft")
		self.vehicleSeat = nil
	end
end

function VehicleController:OnThrottleAccel(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
		self.acceleration = 0
	else
		self.acceleration = -1
	end
	self.throttle = self.acceleration + self.decceleration
end

function VehicleController:OnThrottleDeccel(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
		self.decceleration = 0
	else
		self.decceleration = 1
	end
	self.throttle = self.acceleration + self.decceleration
end

function VehicleController:OnSteerRight(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
		self.turningRight = 0
	else
		self.turningRight = 1
	end
	self.steer = self.turningRight + self.turningLeft
end

function VehicleController:OnSteerLeft(actionName, inputState, inputObject)
	if inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
		self.turningLeft = 0
	else
		self.turningLeft = -1
	end
	self.steer = self.turningRight + self.turningLeft
end

-- Call this from a function bound to Renderstep with Input Priority
function VehicleController:Update(moveVector: Vector3, cameraRelative: boolean, usingGamepad: boolean)
	if self.vehicleSeat then
		if cameraRelative then
			-- This is the default steering mode
			moveVector = moveVector + Vector3.new(self.steer, 0, self.throttle)
			if usingGamepad and onlyTriggersForThrottle and useTriggersForThrottle then
				self.vehicleSeat.ThrottleFloat = -self.throttle
			else
				self.vehicleSeat.ThrottleFloat = -moveVector.Z
			end
			self.vehicleSeat.SteerFloat = moveVector.X

			return moveVector, true
		else
			-- This is the path following mode
			local localMoveVector = self.vehicleSeat.Occupant.RootPart.CFrame:VectorToObjectSpace(moveVector)

			self.vehicleSeat.ThrottleFloat = self:ComputeThrottle(localMoveVector)
			self.vehicleSeat.SteerFloat = self:ComputeSteer(localMoveVector)

			return ZERO_VECTOR3, true
		end
	end
	return moveVector, false
end

function VehicleController:ComputeThrottle(localMoveVector)
	if localMoveVector ~= ZERO_VECTOR3 then
		local throttle = -localMoveVector.Z
		return throttle
	else
		return 0.0
	end
end

function VehicleController:ComputeSteer(localMoveVector)
	if localMoveVector ~= ZERO_VECTOR3 then
		local steerAngle = -math.atan2(-localMoveVector.x, -localMoveVector.z) * (180 / math.pi)
		return steerAngle / self.autoPilot.MaxSteeringAngle
	else
		return 0.0
	end
end

function VehicleController:SetupAutoPilot()
	-- Setup default
	self.autoPilot.MaxSpeed = self.vehicleSeat.MaxSpeed
	self.autoPilot.MaxSteeringAngle = AUTO_PILOT_DEFAULT_MAX_STEERING_ANGLE

	-- VehicleSeat should have a MaxSteeringAngle as well.
	-- Or we could look for a child "AutoPilotConfigModule" to find these values
	-- Or allow developer to set them through the API as like the CLickToMove customization API
end

return VehicleController


--!nonstrict
--[[
	ControlModule - This ModuleScript implements a singleton class to manage the
	selection, activation, and deactivation of the current character movement controller.
	This script binds to RenderStepped at Input priority and calls the Update() methods
	on the active controller instances.

	The character controller ModuleScripts implement classes which are instantiated and
	activated as-needed, they are no longer all instantiated up front as they were in
	the previous generation of PlayerScripts.

	2018 PlayerScripts Update - AllYourBlox
--]]
local ControlModule = {}
ControlModule.__index = ControlModule

--[[ Roblox Services ]]--
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local GuiService = game:GetService("GuiService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local UserGameSettings = UserSettings():GetService("UserGameSettings")
local VRService = game:GetService("VRService")

-- Roblox User Input Control Modules - each returns a new() constructor function used to create controllers as needed
local Utils = require(ReplicatedStorage.Modules.Utils)
local Keyboard = require(script:WaitForChild("Keyboard"))
local Gamepad = require(script:WaitForChild("Gamepad"))
local DynamicThumbstick = require(script:WaitForChild("DynamicThumbstick"))

local FFlagUserHideControlsWhenMenuOpen do
	local success, result = pcall(function()
		return UserSettings():IsUserFeatureEnabled("UserHideControlsWhenMenuOpen")
	end)
	FFlagUserHideControlsWhenMenuOpen = success and result
end

local FFlagUserDynamicThumbstickSafeAreaUpdate do
	local success, result = pcall(function()
		return UserSettings():IsUserFeatureEnabled("UserDynamicThumbstickSafeAreaUpdate")
	end)
	FFlagUserDynamicThumbstickSafeAreaUpdate = success and result
end

local TouchThumbstick = require(script:WaitForChild("TouchThumbstick"))

-- These controllers handle only walk/run movement, jumping is handled by the
-- TouchJump controller if any of these are active
local ClickToMove = require(script:WaitForChild("ClickToMoveController"))
local TouchJump = require(script:WaitForChild("TouchJump"))

local VehicleController = require(script:WaitForChild("VehicleController"))

local CONTROL_ACTION_PRIORITY = Enum.ContextActionPriority.Medium.Value

-- Mapping from movement mode and lastInputType enum values to control modules to avoid huge if elseif switching
local movementEnumToModuleMap = {
	[Enum.TouchMovementMode.DPad] = DynamicThumbstick,
	[Enum.DevTouchMovementMode.DPad] = DynamicThumbstick,
	[Enum.TouchMovementMode.Thumbpad] = DynamicThumbstick,
	[Enum.DevTouchMovementMode.Thumbpad] = DynamicThumbstick,
	[Enum.TouchMovementMode.Thumbstick] = TouchThumbstick,
	[Enum.DevTouchMovementMode.Thumbstick] = TouchThumbstick,
	[Enum.TouchMovementMode.DynamicThumbstick] = DynamicThumbstick,
	[Enum.DevTouchMovementMode.DynamicThumbstick] = DynamicThumbstick,
	[Enum.TouchMovementMode.ClickToMove] = ClickToMove,
	[Enum.DevTouchMovementMode.ClickToMove] = ClickToMove,

	-- Current default
	[Enum.TouchMovementMode.Default] = DynamicThumbstick,

	[Enum.ComputerMovementMode.Default] = Keyboard,
	[Enum.ComputerMovementMode.KeyboardMouse] = Keyboard,
	[Enum.DevComputerMovementMode.KeyboardMouse] = Keyboard,
	[Enum.DevComputerMovementMode.Scriptable] = nil,
	[Enum.ComputerMovementMode.ClickToMove] = ClickToMove,
	[Enum.DevComputerMovementMode.ClickToMove] = ClickToMove,
}

-- Keyboard controller is really keyboard and mouse controller
local computerInputTypeToModuleMap = {
	[Enum.UserInputType.Keyboard] = Keyboard,
	[Enum.UserInputType.MouseButton1] = Keyboard,
	[Enum.UserInputType.MouseButton2] = Keyboard,
	[Enum.UserInputType.MouseButton3] = Keyboard,
	[Enum.UserInputType.MouseWheel] = Keyboard,
	[Enum.UserInputType.MouseMovement] = Keyboard,
	[Enum.UserInputType.Gamepad1] = Gamepad,
	[Enum.UserInputType.Gamepad2] = Gamepad,
	[Enum.UserInputType.Gamepad3] = Gamepad,
	[Enum.UserInputType.Gamepad4] = Gamepad,
}

local lastInputType

function ControlModule.new()
	local self = setmetatable({},ControlModule)

	-- The Modules above are used to construct controller instances as-needed, and this
	-- table is a map from Module to the instance created from it
	self.controllers = {}

	self.activeControlModule = nil	-- Used to prevent unnecessarily expensive checks on each input event
	self.activeController = nil
	self.touchJumpController = nil
	self.moveFunction = Players.LocalPlayer.Move
	self.humanoid = nil
	self.lastInputType = Enum.UserInputType.None
	self.controlsEnabled = true

	-- For Roblox self.vehicleController
	self.humanoidSeatedConn = nil
	self.vehicleController = nil

	self.touchControlFrame = nil
	self.currentTorsoAngle = 0

	if FFlagUserHideControlsWhenMenuOpen then
		GuiService.MenuOpened:Connect(function()
			if self.touchControlFrame and self.touchControlFrame.Visible then
				self.touchControlFrame.Visible = false
			end
		end)

		GuiService.MenuClosed:Connect(function()
			if self.touchControlFrame then
				self.touchControlFrame.Visible = true
			end
		end)
	end

	self.vehicleController = VehicleController.new(CONTROL_ACTION_PRIORITY)

	Utils.Character.ObserveCharacter(Players.LocalPlayer, function(char: Model) self:OnCharacterAdded(char) end)
	Players.LocalPlayer.CharacterRemoving:Connect(function(char) self:OnCharacterRemoving(char) end)

	RunService:BindToRenderStep("ControlScriptRenderstep", Enum.RenderPriority.Input.Value, function(dt)
		self:OnRenderStepped(dt)
	end)

	UserInputService.LastInputTypeChanged:Connect(function(newLastInputType)
		self:OnLastInputTypeChanged(newLastInputType)
	end)

	UserGameSettings:GetPropertyChangedSignal("TouchMovementMode"):Connect(function()
		self:OnTouchMovementModeChange()
	end)
	Players.LocalPlayer:GetPropertyChangedSignal("DevTouchMovementMode"):Connect(function()
		self:OnTouchMovementModeChange()
	end)

	UserGameSettings:GetPropertyChangedSignal("ComputerMovementMode"):Connect(function()
		self:OnComputerMovementModeChange()
	end)
	Players.LocalPlayer:GetPropertyChangedSignal("DevComputerMovementMode"):Connect(function()
		self:OnComputerMovementModeChange()
	end)

	--[[ Touch Device UI ]]--
	self.playerGui = nil
	self.touchGui = nil
	self.playerGuiAddedConn = nil

	GuiService:GetPropertyChangedSignal("TouchControlsEnabled"):Connect(function()
		self:UpdateTouchGuiVisibility()
		self:UpdateActiveControlModuleEnabled()
	end)

	if UserInputService.TouchEnabled then
		self.playerGui = Players.LocalPlayer:FindFirstChildWhichIsA("PlayerGui")
		if self.playerGui then
			self:CreateTouchGuiContainer()
			self:OnLastInputTypeChanged(UserInputService:GetLastInputType())
		else
			self.playerGuiAddedConn = Players.LocalPlayer.ChildAdded:Connect(function(child)
				if child:IsA("PlayerGui") then
					self.playerGui = child
					self:CreateTouchGuiContainer()
					self.playerGuiAddedConn:Disconnect()
					self.playerGuiAddedConn = nil
					self:OnLastInputTypeChanged(UserInputService:GetLastInputType())
				end
			end)
		end
	else
		self:OnLastInputTypeChanged(UserInputService:GetLastInputType())
	end

	return self
end

-- Convenience function so that calling code does not have to first get the activeController
-- and then call GetMoveVector on it. When there is no active controller, this function returns the
-- zero vector
function ControlModule:GetMoveVector(): Vector3
	if self.activeController then
		return self.activeController:GetMoveVector()
	end
	return Vector3.new(0,0,0)
end

local function NormalizeAngle(angle): number
	angle = (angle + math.pi*4) % (math.pi*2)
	if angle > math.pi then
		angle = angle - math.pi*2
	end
	return angle
end

local function AverageAngle(angleA, angleB): number
	local difference = NormalizeAngle(angleB - angleA)
	return NormalizeAngle(angleA + difference/2)
end

function ControlModule:GetEstimatedVRTorsoFrame(): CFrame
	local headFrame = VRService:GetUserCFrame(Enum.UserCFrame.Head)
	local _, headAngle, _ = headFrame:ToEulerAnglesYXZ()
	headAngle = -headAngle
	if not VRService:GetUserCFrameEnabled(Enum.UserCFrame.RightHand) or
		not VRService:GetUserCFrameEnabled(Enum.UserCFrame.LeftHand) then
		self.currentTorsoAngle = headAngle;
	else
		local leftHandPos = VRService:GetUserCFrame(Enum.UserCFrame.LeftHand)
		local rightHandPos = VRService:GetUserCFrame(Enum.UserCFrame.RightHand)

		local leftHandToHead = headFrame.Position - leftHandPos.Position
		local rightHandToHead = headFrame.Position - rightHandPos.Position
		local leftHandAngle = -math.atan2(leftHandToHead.X, leftHandToHead.Z)
		local rightHandAngle = -math.atan2(rightHandToHead.X, rightHandToHead.Z)
		local averageHandAngle = AverageAngle(leftHandAngle, rightHandAngle)

		local headAngleRelativeToCurrentAngle = NormalizeAngle(headAngle - self.currentTorsoAngle)
		local averageHandAngleRelativeToCurrentAngle = NormalizeAngle(averageHandAngle - self.currentTorsoAngle)

		local averageHandAngleValid =
			averageHandAngleRelativeToCurrentAngle > -math.pi/2 and
			averageHandAngleRelativeToCurrentAngle < math.pi/2

		if not averageHandAngleValid then
			averageHandAngleRelativeToCurrentAngle = headAngleRelativeToCurrentAngle
		end

		local minimumValidAngle = math.min(averageHandAngleRelativeToCurrentAngle, headAngleRelativeToCurrentAngle)
		local maximumValidAngle = math.max(averageHandAngleRelativeToCurrentAngle, headAngleRelativeToCurrentAngle)

		local relativeAngleToUse = 0
		if minimumValidAngle > 0 then
			relativeAngleToUse = minimumValidAngle
		elseif maximumValidAngle < 0 then
			relativeAngleToUse = maximumValidAngle
		end

		self.currentTorsoAngle = relativeAngleToUse + self.currentTorsoAngle
	end

	return CFrame.new(headFrame.Position) * CFrame.fromEulerAnglesYXZ(0, -self.currentTorsoAngle, 0)
end

function ControlModule:GetActiveController()
	return self.activeController
end

-- Checks for conditions for enabling/disabling the active controller and updates whether the active controller is enabled/disabled
function ControlModule:UpdateActiveControlModuleEnabled()
	-- helpers for disable/enable
	local disable = function()
		self.activeController:Enable(false)

		if self.moveFunction then
			self.moveFunction(Players.LocalPlayer, Vector3.new(0,0,0), true)
		end
	end

	local enable = function()
		if self.activeControlModule == ClickToMove then
			-- For ClickToMove, when it is the player's choice, we also enable the full keyboard controls.
			-- When the developer is forcing click to move, the most keyboard controls (WASD) are not available, only jump.
			self.activeController:Enable(
				true,
				Players.LocalPlayer.DevComputerMovementMode == Enum.DevComputerMovementMode.UserChoice,
				self.touchJumpController
			)
		elseif self.touchControlFrame then
			self.activeController:Enable(true, self.touchControlFrame)
		else
			self.activeController:Enable(true)
		end
	end

	-- there is no active controller
	if not self.activeController then
		return
	end

	-- developer called ControlModule:Disable(), don't turn back on
	if not self.controlsEnabled then
		disable()
		return
	end

	-- GuiService.TouchControlsEnabled == false and the active controller is a touch controller,
	-- disable controls
	if not GuiService.TouchControlsEnabled and UserInputService.TouchEnabled and
		(self.activeControlModule == ClickToMove or self.activeControlModule == TouchThumbstick or
			self.activeControlModule == DynamicThumbstick) then
		disable()
		return
	end

	-- no settings prevent enabling controls
	enable()
end

function ControlModule:Enable(enable: boolean?)
	if enable == nil then
		enable = true
	end
	self.controlsEnabled = enable

	if not self.activeController then
		return
	end

	self:UpdateActiveControlModuleEnabled()
end

-- For those who prefer distinct functions
function ControlModule:Disable()
	self.controlsEnabled = false

	self:UpdateActiveControlModuleEnabled()
end

-- Returns module (possibly nil) and success code to differentiate returning nil due to error vs Scriptable
function ControlModule:SelectComputerMovementModule(): ({}?, boolean)
	if not (UserInputService.KeyboardEnabled or UserInputService.GamepadEnabled) then
		return nil, false
	end

	local computerModule
	local DevMovementMode = Players.LocalPlayer.DevComputerMovementMode

	if DevMovementMode == Enum.DevComputerMovementMode.UserChoice then
		computerModule = computerInputTypeToModuleMap[lastInputType]
		if UserGameSettings.ComputerMovementMode == Enum.ComputerMovementMode.ClickToMove and computerModule == Keyboard then
			-- User has ClickToMove set in Settings, prefer ClickToMove controller for keyboard and mouse lastInputTypes
			computerModule = ClickToMove
		end
	else
		-- Developer has selected a mode that must be used.
		computerModule = movementEnumToModuleMap[DevMovementMode]

		-- computerModule is expected to be nil here only when developer has selected Scriptable
		if (not computerModule) and DevMovementMode ~= Enum.DevComputerMovementMode.Scriptable then
			warn("No character control module is associated with DevComputerMovementMode ", DevMovementMode)
		end
	end

	if computerModule then
		return computerModule, true
	elseif DevMovementMode == Enum.DevComputerMovementMode.Scriptable then
		-- Special case where nil is returned and we actually want to set self.activeController to nil for Scriptable
		return nil, true
	else
		-- This case is for when computerModule is nil because of an error and no suitable control module could
		-- be found.
		return nil, false
	end
end

-- Choose current Touch control module based on settings (user, dev)
-- Returns module (possibly nil) and success code to differentiate returning nil due to error vs Scriptable
function ControlModule:SelectTouchModule(): ({}?, boolean)
	if not UserInputService.TouchEnabled then
		return nil, false
	end
	local touchModule
	local DevMovementMode = Players.LocalPlayer.DevTouchMovementMode
	if DevMovementMode == Enum.DevTouchMovementMode.UserChoice then
		touchModule = movementEnumToModuleMap[UserGameSettings.TouchMovementMode]
	elseif DevMovementMode == Enum.DevTouchMovementMode.Scriptable then
		return nil, true
	else
		touchModule = movementEnumToModuleMap[DevMovementMode]
	end
	return touchModule, true
end

local function getGamepadRightThumbstickPosition(): Vector3
	local state = UserInputService:GetGamepadState(Enum.UserInputType.Gamepad1)
	for _, input in pairs(state) do
		if input.KeyCode == Enum.KeyCode.Thumbstick2 then
			return input.Position
		end
	end
	return Vector3.new(0,0,0)
end

function ControlModule:calculateRawMoveVector(humanoid: Humanoid, cameraRelativeMoveVector: Vector3): Vector3
	local camera = Workspace.CurrentCamera
	if not camera then
		return cameraRelativeMoveVector
	end
	local cameraCFrame = camera.CFrame

	if VRService.VREnabled and humanoid.RootPart then
		local vrFrame = VRService:GetUserCFrame(Enum.UserCFrame.Head)

		vrFrame = self:GetEstimatedVRTorsoFrame()

		-- movement relative to VR frustum
		local cameraDelta = camera.Focus.Position - cameraCFrame.Position
		if cameraDelta.Magnitude < 3 then -- "nearly" first person
			cameraCFrame = cameraCFrame * vrFrame
		else
			cameraCFrame = camera.CFrame * (vrFrame.Rotation + vrFrame.Position * camera.HeadScale)
		end
	end

	if humanoid:GetState() == Enum.HumanoidStateType.Swimming then
		if VRService.VREnabled then
			cameraRelativeMoveVector = Vector3.new(cameraRelativeMoveVector.X, 0, cameraRelativeMoveVector.Z)
			if cameraRelativeMoveVector.Magnitude < 0.01 then
				return Vector3.zero
			end

			local pitch = -getGamepadRightThumbstickPosition().Y * math.rad(80)
			local yawAngle = math.atan2(-cameraRelativeMoveVector.X, -cameraRelativeMoveVector.Z)
			local _, cameraYaw, _ = cameraCFrame:ToEulerAnglesYXZ()
			yawAngle += cameraYaw
			local movementCFrame = CFrame.fromEulerAnglesYXZ(pitch, yawAngle, 0)
			return movementCFrame.LookVector
		else
			return cameraCFrame:VectorToWorldSpace(cameraRelativeMoveVector)
		end
	end

	local c, s
	local _, _, _, R00, R01, R02, _, _, R12, _, _, R22 = cameraCFrame:GetComponents()
	if R12 < 1 and R12 > -1 then
		-- X and Z components from back vector.
		c = R22
		s = R02
	else
		-- In this case the camera is looking straight up or straight down.
		-- Use X components from right and up vectors.
		c = R00
		s = -R01*math.sign(R12)
	end
	local norm = math.sqrt(c*c + s*s)
	return Vector3.new(
		(c*cameraRelativeMoveVector.X + s*cameraRelativeMoveVector.Z)/norm,
		0,
		(c*cameraRelativeMoveVector.Z - s*cameraRelativeMoveVector.X)/norm
	)
end

function ControlModule:OnRenderStepped(dt)
	if self.activeController and self.activeController.enabled and self.humanoid then
		-- Give the controller a chance to adjust its state
		self.activeController:OnRenderStepped(dt)

		-- Now retrieve info from the controller
		local moveVector = self.activeController:GetMoveVector()
		local cameraRelative = self.activeController:IsMoveVectorCameraRelative()

		local clickToMoveController = self:GetClickToMoveController()
		if self.activeController ~= clickToMoveController then
			if moveVector.magnitude > 0 then
				-- Clean up any developer started MoveTo path
				clickToMoveController:CleanupPath()
			else
				-- Get move vector for developer started MoveTo
				clickToMoveController:OnRenderStepped(dt)
				moveVector = clickToMoveController:GetMoveVector()
				cameraRelative = clickToMoveController:IsMoveVectorCameraRelative()
			end
		end

		-- Are we driving a vehicle ?
		local _vehicleConsumedInput = false
		if self.vehicleController then
			moveVector, _vehicleConsumedInput = self.vehicleController:Update(moveVector, cameraRelative, self.activeControlModule==Gamepad)
		end

		-- If not, move the player
		-- Verification of _vehicleConsumedInput is commented out to preserve legacy behavior,
		-- in case some game relies on Humanoid.MoveDirection still being set while in a VehicleSeat
		--if not _vehicleConsumedInput then
		if cameraRelative then
			moveVector = self:calculateRawMoveVector(self.humanoid, moveVector)
		end
		self.moveFunction(Players.LocalPlayer, moveVector, false)
		--end

		-- And make them jump if needed
		self.humanoid.Jump = self.activeController:GetIsJumping() or (self.touchJumpController and self.touchJumpController:GetIsJumping())
	end
end

function ControlModule:OnHumanoidSeated(active: boolean, currentSeatPart: BasePart)
	if active then
		if currentSeatPart and currentSeatPart:IsA("VehicleSeat") then
			if not self.vehicleController then
				self.vehicleController = self.vehicleController.new(CONTROL_ACTION_PRIORITY)
			end
			self.vehicleController:Enable(true, currentSeatPart)
		end
	else
		if self.vehicleController then
			self.vehicleController:Enable(false, currentSeatPart)
		end
	end
end

function ControlModule:OnCharacterAdded(char)
	self.humanoid = char:FindFirstChildWhichIsA("Humanoid")
	while not self.humanoid do
		char.ChildAdded:wait()
		self.humanoid = char:FindFirstChildWhichIsA("Humanoid")
	end

	self:UpdateTouchGuiVisibility()

	if self.humanoidSeatedConn then
		self.humanoidSeatedConn:Disconnect()
		self.humanoidSeatedConn = nil
	end
	self.humanoidSeatedConn = self.humanoid.Seated:Connect(function(active, currentSeatPart)
		self:OnHumanoidSeated(active, currentSeatPart)
	end)
end

function ControlModule:OnCharacterRemoving(char)
	self.humanoid = nil

	self:UpdateTouchGuiVisibility()
end

function ControlModule:UpdateTouchGuiVisibility()
	if self.touchGui then
		local doShow = self.humanoid and GuiService.TouchControlsEnabled
		self.touchGui.Enabled = not not doShow -- convert to bool
	end
end

-- Helper function to lazily instantiate a controller if it does not yet exist,
-- disable the active controller if it is different from the on being switched to,
-- and then enable the requested controller. The argument to this function must be
-- a reference to one of the control modules, i.e. Keyboard, Gamepad, etc.

-- This function should handle all controller enabling and disabling without relying on
-- ControlModule:Enable() and Disable()
function ControlModule:SwitchToController(controlModule)
	-- controlModule is invalid, just disable current controller
	if not controlModule then
		if self.activeController then
			self.activeController:Enable(false)
		end
		self.activeController = nil
		self.activeControlModule = nil
		return
	end

	-- first time switching to this control module, should instantiate it
	if not self.controllers[controlModule] then
		self.controllers[controlModule] = controlModule.new(CONTROL_ACTION_PRIORITY)
	end

	-- switch to the new controlModule
	if self.activeController ~= self.controllers[controlModule] then
		if self.activeController then
			self.activeController:Enable(false)
		end
		self.activeController = self.controllers[controlModule]
		self.activeControlModule = controlModule -- Only used to check if controller switch is necessary

		if self.touchControlFrame and (self.activeControlModule == ClickToMove
			or self.activeControlModule == TouchThumbstick
			or self.activeControlModule == DynamicThumbstick) then
			if not self.controllers[TouchJump] then
				self.controllers[TouchJump] = TouchJump.new()
			end
			self.touchJumpController = self.controllers[TouchJump]
			self.touchJumpController:Enable(true, self.touchControlFrame)
		else
			if self.touchJumpController then
				self.touchJumpController:Enable(false)
			end
		end

		self:UpdateActiveControlModuleEnabled()
	end
end

function ControlModule:OnLastInputTypeChanged(newLastInputType)
	if lastInputType == newLastInputType then
		warn("LastInputType Change listener called with current type.")
	end
	lastInputType = newLastInputType

	if lastInputType == Enum.UserInputType.Touch then
		-- TODO: Check if touch module already active
		local touchModule, success = self:SelectTouchModule()
		if success then
			while not self.touchControlFrame do
				task.wait()
			end
			self:SwitchToController(touchModule)
		end
	elseif computerInputTypeToModuleMap[lastInputType] ~= nil then
		local computerModule = self:SelectComputerMovementModule()
		if computerModule then
			self:SwitchToController(computerModule)
		end
	end

	self:UpdateTouchGuiVisibility()
end

-- Called when any relevant values of GameSettings or LocalPlayer change, forcing re-evalulation of
-- current control scheme
function ControlModule:OnComputerMovementModeChange()
	local controlModule, success =  self:SelectComputerMovementModule()
	if success then
		self:SwitchToController(controlModule)
	end
end

function ControlModule:OnTouchMovementModeChange()
	local touchModule, success = self:SelectTouchModule()
	if success then
		while not self.touchControlFrame do
			task.wait()
		end
		self:SwitchToController(touchModule)
	end
end

function ControlModule:CreateTouchGuiContainer()
	if self.touchGui then self.touchGui:Destroy() end

	-- Container for all touch device guis
	self.touchGui = Instance.new("ScreenGui")
	self.touchGui.Name = "TouchGui"
	self.touchGui.ResetOnSpawn = false
	self.touchGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
	self:UpdateTouchGuiVisibility()

	if FFlagUserDynamicThumbstickSafeAreaUpdate then
		self.touchGui.ClipToDeviceSafeArea = false
	end

	self.touchControlFrame = Instance.new("Frame")
	self.touchControlFrame.Name = "TouchControlFrame"
	self.touchControlFrame.Size = UDim2.new(1, 0, 1, 0)
	self.touchControlFrame.BackgroundTransparency = 1
	self.touchControlFrame.Parent = self.touchGui

	self.touchGui.Parent = self.playerGui
end

function ControlModule:GetClickToMoveController()
	if not self.controllers[ClickToMove] then
		self.controllers[ClickToMove] = ClickToMove.new(CONTROL_ACTION_PRIORITY)
	end
	return self.controllers[ClickToMove]
end

return ControlModule.new()


--[[
	PlayerModule - This module requires and instantiates the camera and control modules,
	and provides getters for developers to access methods on these singletons without
	having to modify Roblox-supplied scripts.

	2018 PlayerScripts Update - AllYourBlox
--]]

local PlayerModule = {}
PlayerModule.__index = PlayerModule

function PlayerModule.new()
	local self = setmetatable({},PlayerModule)
	self.cameras = require(script:WaitForChild("CameraModule"))
	self.controls = require(script:WaitForChild("ControlModule"))
	return self
end

function PlayerModule:GetCameras()
	return self.cameras
end

function PlayerModule:GetControls()
	return self.controls
end

function PlayerModule:GetClickToMoveController()
	return self.controls:GetClickToMoveController()
end

return PlayerModule.new()