· 4 years ago · Apr 01, 2021, 06:46 AM
1--[[
2local _p = game:WaitForChild("Players")
3local _plr = _p.ChildAdded:Wait()
4if _plr == _p.LocalPlayer then
5	_plr.ChildAdded:Connect(function(cccc)
6		if c.Name == "PlayerScriptsLoader" then
7			c.Disabled = true
8		end
9	end)
10end
11]]
12repeat wait()
13a = pcall(function()
14	game:WaitForChild("Players").LocalPlayer:WaitForChild("PlayerScripts").ChildAdded:Connect(function(c)
15		if c.Name == "PlayerScriptsLoader"then
16			c.Disabled = true
17		end
18	end)
19	end)
20	if a == true then break end
21until true == false
22game:WaitForChild("Players").LocalPlayer:WaitForChild("PlayerScripts").ChildAdded:Connect(function(c)
23	if c.Name == "PlayerScriptsLoader"then
24		c.Disabled = true
25	end
26end)
27
28
29function _CameraUI()
30	local Players = game:GetService("Players")
31	local TweenService = game:GetService("TweenService")
32	
33	local LocalPlayer = Players.LocalPlayer
34	if not LocalPlayer then
35		Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
36		LocalPlayer = Players.LocalPlayer
37	end
38	
39	local function waitForChildOfClass(parent, class)
40		local child = parent:FindFirstChildOfClass(class)
41		while not child or child.ClassName ~= class do
42			child = parent.ChildAdded:Wait()
43		end
44		return child
45	end
46	
47	local PlayerGui = waitForChildOfClass(LocalPlayer, "PlayerGui")
48	
49	local TOAST_OPEN_SIZE = UDim2.new(0, 326, 0, 58)
50	local TOAST_CLOSED_SIZE = UDim2.new(0, 80, 0, 58)
51	local TOAST_BACKGROUND_COLOR = Color3.fromRGB(32, 32, 32)
52	local TOAST_BACKGROUND_TRANS = 0.4
53	local TOAST_FOREGROUND_COLOR = Color3.fromRGB(200, 200, 200)
54	local TOAST_FOREGROUND_TRANS = 0
55	
56	-- Convenient syntax for creating a tree of instanes
57	local function create(className)
58		return function(props)
59			local inst = Instance.new(className)
60			local parent = props.Parent
61			props.Parent = nil
62			for name, val in pairs(props) do
63				if type(name) == "string" then
64					inst[name] = val
65				else
66					val.Parent = inst
67				end
68			end
69			-- Only set parent after all other properties are initialized
70			inst.Parent = parent
71			return inst
72		end
73	end
74	
75	local initialized = false
76	
77	local uiRoot
78	local toast
79	local toastIcon
80	local toastUpperText
81	local toastLowerText
82	
83	local function initializeUI()
84		assert(not initialized)
85	
86		uiRoot = create("ScreenGui"){
87			Name = "RbxCameraUI",
88			AutoLocalize = false,
89			Enabled = true,
90			DisplayOrder = -1, -- Appears behind default developer UI
91			IgnoreGuiInset = false,
92			ResetOnSpawn = false,
93			ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
94	
95			create("ImageLabel"){
96				Name = "Toast",
97				Visible = false,
98				AnchorPoint = Vector2.new(0.5, 0),
99				BackgroundTransparency = 1,
100				BorderSizePixel = 0,
101				Position = UDim2.new(0.5, 0, 0, 8),
102				Size = TOAST_CLOSED_SIZE,
103				Image = "rbxasset://textures/ui/Camera/CameraToast9Slice.png",
104				ImageColor3 = TOAST_BACKGROUND_COLOR,
105				ImageRectSize = Vector2.new(6, 6),
106				ImageTransparency = 1,
107				ScaleType = Enum.ScaleType.Slice,
108				SliceCenter = Rect.new(3, 3, 3, 3),
109				ClipsDescendants = true,
110	
111				create("Frame"){
112					Name = "IconBuffer",
113					BackgroundTransparency = 1,
114					BorderSizePixel = 0,
115					Position = UDim2.new(0, 0, 0, 0),
116					Size = UDim2.new(0, 80, 1, 0),
117	
118					create("ImageLabel"){
119						Name = "Icon",
120						AnchorPoint = Vector2.new(0.5, 0.5),
121						BackgroundTransparency = 1,
122						Position = UDim2.new(0.5, 0, 0.5, 0),
123						Size = UDim2.new(0, 48, 0, 48),
124						ZIndex = 2,
125						Image = "rbxasset://textures/ui/Camera/CameraToastIcon.png",
126						ImageColor3 = TOAST_FOREGROUND_COLOR,
127						ImageTransparency = 1,
128					}
129				},
130	
131				create("Frame"){
132					Name = "TextBuffer",
133					BackgroundTransparency = 1,
134					BorderSizePixel = 0,
135					Position = UDim2.new(0, 80, 0, 0),
136					Size = UDim2.new(1, -80, 1, 0),
137					ClipsDescendants = true,
138	
139					create("TextLabel"){
140						Name = "Upper",
141						AnchorPoint = Vector2.new(0, 1),
142						BackgroundTransparency = 1,
143						Position = UDim2.new(0, 0, 0.5, 0),
144						Size = UDim2.new(1, 0, 0, 19),
145						Font = Enum.Font.GothamSemibold,
146						Text = "Camera control enabled",
147						TextColor3 = TOAST_FOREGROUND_COLOR,
148						TextTransparency = 1,
149						TextSize = 19,
150						TextXAlignment = Enum.TextXAlignment.Left,
151						TextYAlignment = Enum.TextYAlignment.Center,
152					},
153	
154					create("TextLabel"){
155						Name = "Lower",
156						AnchorPoint = Vector2.new(0, 0),
157						BackgroundTransparency = 1,
158						Position = UDim2.new(0, 0, 0.5, 3),
159						Size = UDim2.new(1, 0, 0, 15),
160						Font = Enum.Font.Gotham,
161						Text = "Right mouse button to toggle",
162						TextColor3 = TOAST_FOREGROUND_COLOR,
163						TextTransparency = 1,
164						TextSize = 15,
165						TextXAlignment = Enum.TextXAlignment.Left,
166						TextYAlignment = Enum.TextYAlignment.Center,
167					},
168				},
169			},
170	
171			Parent = PlayerGui,
172		}
173	
174		toast = uiRoot.Toast
175		toastIcon = toast.IconBuffer.Icon
176		toastUpperText = toast.TextBuffer.Upper
177		toastLowerText = toast.TextBuffer.Lower
178	
179		initialized = true
180	end
181	
182	local CameraUI = {}
183	
184	do
185		-- Instantaneously disable the toast or enable for opening later on. Used when switching camera modes.
186		function CameraUI.setCameraModeToastEnabled(enabled)
187			if not enabled and not initialized then
188				return
189			end
190	
191			if not initialized then
192				initializeUI()
193			end
194	
195			toast.Visible = enabled
196			if not enabled then
197				CameraUI.setCameraModeToastOpen(false)
198			end
199		end
200	
201		local tweenInfo = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
202	
203		-- Tween the toast in or out. Toast must be enabled with setCameraModeToastEnabled.
204		function CameraUI.setCameraModeToastOpen(open)
205			assert(initialized)
206	
207			TweenService:Create(toast, tweenInfo, {
208				Size = open and TOAST_OPEN_SIZE or TOAST_CLOSED_SIZE,
209				ImageTransparency = open and TOAST_BACKGROUND_TRANS or 1,
210			}):Play()
211	
212			TweenService:Create(toastIcon, tweenInfo, {
213				ImageTransparency = open and TOAST_FOREGROUND_TRANS or 1,
214			}):Play()
215	
216			TweenService:Create(toastUpperText, tweenInfo, {
217				TextTransparency = open and TOAST_FOREGROUND_TRANS or 1,
218			}):Play()
219	
220			TweenService:Create(toastLowerText, tweenInfo, {
221				TextTransparency = open and TOAST_FOREGROUND_TRANS or 1,
222			}):Play()
223		end
224	end
225	
226	return CameraUI
227end
228
229function _CameraToggleStateController()
230	local Players = game:GetService("Players")
231	local UserInputService = game:GetService("UserInputService")
232	local GameSettings = UserSettings():GetService("UserGameSettings")
233	
234	local LocalPlayer = Players.LocalPlayer
235	if not LocalPlayer then
236		Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
237		LocalPlayer = Players.LocalPlayer
238	end
239	
240	local Mouse = LocalPlayer:GetMouse()
241	
242	local Input = _CameraInput()
243	local CameraUI = _CameraUI()
244	
245	local lastTogglePan = false
246	local lastTogglePanChange = tick()
247	
248	local CROSS_MOUSE_ICON = "rbxasset://textures/Cursors/CrossMouseIcon.png"
249	
250	local lockStateDirty = false
251	local wasTogglePanOnTheLastTimeYouWentIntoFirstPerson = false
252	local lastFirstPerson = false
253	
254	CameraUI.setCameraModeToastEnabled(false)
255	
256	return function(isFirstPerson)
257		local togglePan = Input.getTogglePan()
258		local toastTimeout = 3
259	
260		if isFirstPerson and togglePan ~= lastTogglePan then
261			lockStateDirty = true
262		end
263	
264		if lastTogglePan ~= togglePan or tick() - lastTogglePanChange > toastTimeout then
265			local doShow = togglePan and tick() - lastTogglePanChange < toastTimeout
266	
267			CameraUI.setCameraModeToastOpen(doShow)
268	
269			if togglePan then
270				lockStateDirty = false
271			end
272			lastTogglePanChange = tick()
273			lastTogglePan = togglePan
274		end
275	
276		if isFirstPerson ~= lastFirstPerson then
277			if isFirstPerson then
278				wasTogglePanOnTheLastTimeYouWentIntoFirstPerson = Input.getTogglePan()
279				Input.setTogglePan(true)
280			elseif not lockStateDirty then
281				Input.setTogglePan(wasTogglePanOnTheLastTimeYouWentIntoFirstPerson)
282			end
283		end
284	
285		if isFirstPerson then
286			if Input.getTogglePan() then
287				Mouse.Icon = CROSS_MOUSE_ICON
288				UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
289				--GameSettings.RotationType = Enum.RotationType.CameraRelative
290			else
291				Mouse.Icon = ""
292				UserInputService.MouseBehavior = Enum.MouseBehavior.Default
293				--GameSettings.RotationType = Enum.RotationType.CameraRelative
294			end
295	
296		elseif Input.getTogglePan() then
297			Mouse.Icon = CROSS_MOUSE_ICON
298			UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
299			GameSettings.RotationType = Enum.RotationType.MovementRelative
300	
301		elseif Input.getHoldPan() then
302			Mouse.Icon = ""
303			UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
304			GameSettings.RotationType = Enum.RotationType.MovementRelative
305	
306		else
307			Mouse.Icon = ""
308			UserInputService.MouseBehavior = Enum.MouseBehavior.Default
309			GameSettings.RotationType = Enum.RotationType.MovementRelative
310		end
311	
312		lastFirstPerson = isFirstPerson
313	end
314end
315
316function _CameraInput()
317	local UserInputService = game:GetService("UserInputService")
318	
319	local MB_TAP_LENGTH = 0.3 -- length of time for a short mouse button tap to be registered
320	
321	local rmbDown, rmbUp
322	do
323		local rmbDownBindable = Instance.new("BindableEvent")
324		local rmbUpBindable = Instance.new("BindableEvent")
325	
326		rmbDown = rmbDownBindable.Event
327		rmbUp = rmbUpBindable.Event
328	
329		UserInputService.InputBegan:Connect(function(input, gpe)
330			if not gpe and input.UserInputType == Enum.UserInputType.MouseButton2 then
331				rmbDownBindable:Fire()
332			end
333		end)
334	
335		UserInputService.InputEnded:Connect(function(input, gpe)
336			if input.UserInputType == Enum.UserInputType.MouseButton2 then
337				rmbUpBindable:Fire()
338			end
339		end)
340	end
341	
342	local holdPan = false
343	local togglePan = false
344	local lastRmbDown = 0 -- tick() timestamp of the last right mouse button down event
345	
346	local CameraInput = {}
347	
348	function CameraInput.getHoldPan()
349		return holdPan
350	end
351	
352	function CameraInput.getTogglePan()
353		return togglePan
354	end
355	
356	function CameraInput.getPanning()
357		return togglePan or holdPan
358	end
359	
360	function CameraInput.setTogglePan(value)
361		togglePan = value
362	end
363	
364	local cameraToggleInputEnabled = false
365	local rmbDownConnection
366	local rmbUpConnection
367	
368	function CameraInput.enableCameraToggleInput()
369		if cameraToggleInputEnabled then
370			return
371		end
372		cameraToggleInputEnabled = true
373	
374		holdPan = false
375		togglePan = false
376	
377		if rmbDownConnection then
378			rmbDownConnection:Disconnect()
379		end
380	
381		if rmbUpConnection then
382			rmbUpConnection:Disconnect()
383		end
384	
385		rmbDownConnection = rmbDown:Connect(function()
386			holdPan = true
387			lastRmbDown = tick()
388		end)
389	
390		rmbUpConnection = rmbUp:Connect(function()
391			holdPan = false
392			if tick() - lastRmbDown < MB_TAP_LENGTH and (togglePan or UserInputService:GetMouseDelta().Magnitude < 2) then
393				togglePan = not togglePan
394			end
395		end)
396	end
397	
398	function CameraInput.disableCameraToggleInput()
399		if not cameraToggleInputEnabled then
400			return
401		end
402		cameraToggleInputEnabled = false
403	
404		if rmbDownConnection then
405			rmbDownConnection:Disconnect()
406			rmbDownConnection = nil
407		end
408		if rmbUpConnection then
409			rmbUpConnection:Disconnect()
410			rmbUpConnection = nil
411		end
412	end
413	
414	return CameraInput
415end
416
417function _BaseCamera()
418	--[[
419		BaseCamera - Abstract base class for camera control modules
420		2018 Camera Update - AllYourBlox
421	--]]
422	
423	--[[ Local Constants ]]--
424	local UNIT_Z = Vector3.new(0,0,1)
425	local X1_Y0_Z1 = Vector3.new(1,0,1)	--Note: not a unit vector, used for projecting onto XZ plane
426	
427	local THUMBSTICK_DEADZONE = 0.2
428	local DEFAULT_DISTANCE = 12.5	-- Studs
429	local PORTRAIT_DEFAULT_DISTANCE = 25		-- Studs
430	local FIRST_PERSON_DISTANCE_THRESHOLD = 1.0 -- Below this value, snap into first person
431	
432	local CAMERA_ACTION_PRIORITY = Enum.ContextActionPriority.Default.Value
433	
434	-- Note: DotProduct check in CoordinateFrame::lookAt() prevents using values within about
435	-- 8.11 degrees of the +/- Y axis, that's why these limits are currently 80 degrees
436	local MIN_Y = math.rad(-80)
437	local MAX_Y = math.rad(80)
438	
439	local TOUCH_ADJUST_AREA_UP = math.rad(30)
440	local TOUCH_ADJUST_AREA_DOWN = math.rad(-15)
441	
442	local TOUCH_SENSITIVTY_ADJUST_MAX_Y = 2.1
443	local TOUCH_SENSITIVTY_ADJUST_MIN_Y = 0.5
444	
445	local VR_ANGLE = math.rad(15)
446	local VR_LOW_INTENSITY_ROTATION = Vector2.new(math.rad(15), 0)
447	local VR_HIGH_INTENSITY_ROTATION = Vector2.new(math.rad(45), 0)
448	local VR_LOW_INTENSITY_REPEAT = 0.1
449	local VR_HIGH_INTENSITY_REPEAT = 0.4
450	
451	local ZERO_VECTOR2 = Vector2.new(0,0)
452	local ZERO_VECTOR3 = Vector3.new(0,0,0)
453	
454	local TOUCH_SENSITIVTY = Vector2.new(0.00945 * math.pi, 0.003375 * math.pi)
455	local MOUSE_SENSITIVITY = Vector2.new( 0.002 * math.pi, 0.0015 * math.pi )
456	
457	local SEAT_OFFSET = Vector3.new(0,5,0)
458	local VR_SEAT_OFFSET = Vector3.new(0,4,0)
459	local HEAD_OFFSET = Vector3.new(0,1.5,0)
460	local R15_HEAD_OFFSET = Vector3.new(0, 1.5, 0)
461	local R15_HEAD_OFFSET_NO_SCALING = Vector3.new(0, 2, 0)
462	local HUMANOID_ROOT_PART_SIZE = Vector3.new(2, 2, 1)
463	
464	local GAMEPAD_ZOOM_STEP_1 = 0
465	local GAMEPAD_ZOOM_STEP_2 = 10
466	local GAMEPAD_ZOOM_STEP_3 = 20
467	
468	local PAN_SENSITIVITY = 20
469	local ZOOM_SENSITIVITY_CURVATURE = 0.5
470	
471	local abs = math.abs
472	local sign = math.sign
473	
474	local FFlagUserCameraToggle do
475		local success, result = pcall(function()
476			return UserSettings():IsUserFeatureEnabled("UserCameraToggle")
477		end)
478		FFlagUserCameraToggle = success and result
479	end
480	
481	local FFlagUserDontAdjustSensitvityForPortrait do
482		local success, result = pcall(function()
483			return UserSettings():IsUserFeatureEnabled("UserDontAdjustSensitvityForPortrait")
484		end)
485		FFlagUserDontAdjustSensitvityForPortrait = success and result
486	end
487	
488	local FFlagUserFixZoomInZoomOutDiscrepancy do
489		local success, result = pcall(function()
490			return UserSettings():IsUserFeatureEnabled("UserFixZoomInZoomOutDiscrepancy")
491		end)
492		FFlagUserFixZoomInZoomOutDiscrepancy = success and result
493	end
494	
495	local Util = _CameraUtils()
496	local ZoomController = _ZoomController()
497	local CameraToggleStateController = _CameraToggleStateController()
498	local CameraInput = _CameraInput()
499	local CameraUI = _CameraUI()
500	
501	--[[ Roblox Services ]]--
502	local Players = game:GetService("Players")
503	local UserInputService = game:GetService("UserInputService")
504	local StarterGui = game:GetService("StarterGui")
505	local GuiService = game:GetService("GuiService")
506	local ContextActionService = game:GetService("ContextActionService")
507	local VRService = game:GetService("VRService")
508	local UserGameSettings = UserSettings():GetService("UserGameSettings")
509	
510	local player = Players.LocalPlayer 
511	
512	--[[ The Module ]]--
513	local BaseCamera = {}
514	BaseCamera.__index = BaseCamera
515	
516	function BaseCamera.new()
517		local self = setmetatable({}, BaseCamera)
518	
519		-- So that derived classes have access to this
520		self.FIRST_PERSON_DISTANCE_THRESHOLD = FIRST_PERSON_DISTANCE_THRESHOLD
521	
522		self.cameraType = nil
523		self.cameraMovementMode = nil
524	
525		self.lastCameraTransform = nil
526		self.rotateInput = ZERO_VECTOR2
527		self.userPanningCamera = false
528		self.lastUserPanCamera = tick()
529	
530		self.humanoidRootPart = nil
531		self.humanoidCache = {}
532	
533		-- Subject and position on last update call
534		self.lastSubject = nil
535		self.lastSubjectPosition = Vector3.new(0,5,0)
536	
537		-- These subject distance members refer to the nominal camera-to-subject follow distance that the camera
538		-- is trying to maintain, not the actual measured value.
539		-- The default is updated when screen orientation or the min/max distances change,
540		-- to be sure the default is always in range and appropriate for the orientation.
541		self.defaultSubjectDistance = math.clamp(DEFAULT_DISTANCE, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
542		self.currentSubjectDistance = math.clamp(DEFAULT_DISTANCE, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
543	
544		self.inFirstPerson = false
545		self.inMouseLockedMode = false
546		self.portraitMode = false
547		self.isSmallTouchScreen = false
548	
549		-- Used by modules which want to reset the camera angle on respawn.
550		self.resetCameraAngle = true
551	
552		self.enabled = false
553	
554		-- Input Event Connections
555		self.inputBeganConn = nil
556		self.inputChangedConn = nil
557		self.inputEndedConn = nil
558	
559		self.startPos = nil
560		self.lastPos = nil
561		self.panBeginLook = nil
562	
563		self.panEnabled = true
564		self.keyPanEnabled = true
565		self.distanceChangeEnabled = true
566	
567		self.PlayerGui = nil
568	
569		self.cameraChangedConn = nil
570		self.viewportSizeChangedConn = nil
571	
572		self.boundContextActions = {}
573	
574		-- VR Support
575		self.shouldUseVRRotation = false
576		self.VRRotationIntensityAvailable = false
577		self.lastVRRotationIntensityCheckTime = 0
578		self.lastVRRotationTime = 0
579		self.vrRotateKeyCooldown = {}
580		self.cameraTranslationConstraints = Vector3.new(1, 1, 1)
581		self.humanoidJumpOrigin = nil
582		self.trackingHumanoid = nil
583		self.cameraFrozen = false
584		self.subjectStateChangedConn = nil
585	
586		-- Gamepad support
587		self.activeGamepad = nil
588		self.gamepadPanningCamera = false
589		self.lastThumbstickRotate = nil
590		self.numOfSeconds = 0.7
591		self.currentSpeed = 0
592		self.maxSpeed = 6
593		self.vrMaxSpeed = 4
594		self.lastThumbstickPos = Vector2.new(0,0)
595		self.ySensitivity = 0.65
596		self.lastVelocity = nil
597		self.gamepadConnectedConn = nil
598		self.gamepadDisconnectedConn = nil
599		self.currentZoomSpeed = 1.0
600		self.L3ButtonDown = false
601		self.dpadLeftDown = false
602		self.dpadRightDown = false
603	
604		-- Touch input support
605		self.isDynamicThumbstickEnabled = false
606		self.fingerTouches = {}
607		self.dynamicTouchInput = nil
608		self.numUnsunkTouches = 0
609		self.inputStartPositions = {}
610		self.inputStartTimes = {}
611		self.startingDiff = nil
612		self.pinchBeginZoom = nil
613		self.userPanningTheCamera = false
614		self.touchActivateConn = nil
615	
616		-- Mouse locked formerly known as shift lock mode
617		self.mouseLockOffset = ZERO_VECTOR3
618	
619		-- [[ NOTICE ]] --
620		-- Initialization things used to always execute at game load time, but now these camera modules are instantiated
621		-- when needed, so the code here may run well after the start of the game
622	
623		if player.Character then
624			self:OnCharacterAdded(player.Character)
625		end
626	
627		player.CharacterAdded:Connect(function(char)
628			self:OnCharacterAdded(char)
629		end)
630	
631		if self.cameraChangedConn then self.cameraChangedConn:Disconnect() end
632		self.cameraChangedConn = workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
633			self:OnCurrentCameraChanged()
634		end)
635		self:OnCurrentCameraChanged()
636	
637		if self.playerCameraModeChangeConn then self.playerCameraModeChangeConn:Disconnect() end
638		self.playerCameraModeChangeConn = player:GetPropertyChangedSignal("CameraMode"):Connect(function()
639			self:OnPlayerCameraPropertyChange()
640		end)
641	
642		if self.minDistanceChangeConn then self.minDistanceChangeConn:Disconnect() end
643		self.minDistanceChangeConn = player:GetPropertyChangedSignal("CameraMinZoomDistance"):Connect(function()
644			self:OnPlayerCameraPropertyChange()
645		end)
646	
647		if self.maxDistanceChangeConn then self.maxDistanceChangeConn:Disconnect() end
648		self.maxDistanceChangeConn = player:GetPropertyChangedSignal("CameraMaxZoomDistance"):Connect(function()
649			self:OnPlayerCameraPropertyChange()
650		end)
651	
652		if self.playerDevTouchMoveModeChangeConn then self.playerDevTouchMoveModeChangeConn:Disconnect() end
653		self.playerDevTouchMoveModeChangeConn = player:GetPropertyChangedSignal("DevTouchMovementMode"):Connect(function()
654			self:OnDevTouchMovementModeChanged()
655		end)
656		self:OnDevTouchMovementModeChanged() -- Init
657	
658		if self.gameSettingsTouchMoveMoveChangeConn then self.gameSettingsTouchMoveMoveChangeConn:Disconnect() end
659		self.gameSettingsTouchMoveMoveChangeConn = UserGameSettings:GetPropertyChangedSignal("TouchMovementMode"):Connect(function()
660			self:OnGameSettingsTouchMovementModeChanged()
661		end)
662		self:OnGameSettingsTouchMovementModeChanged() -- Init
663	
664		UserGameSettings:SetCameraYInvertVisible()
665		UserGameSettings:SetGamepadCameraSensitivityVisible()
666	
667		self.hasGameLoaded = game:IsLoaded()
668		if not self.hasGameLoaded then
669			self.gameLoadedConn = game.Loaded:Connect(function()
670				self.hasGameLoaded = true
671				self.gameLoadedConn:Disconnect()
672				self.gameLoadedConn = nil
673			end)
674		end
675	
676		self:OnPlayerCameraPropertyChange()
677	
678		return self
679	end
680	
681	function BaseCamera:GetModuleName()
682		return "BaseCamera"
683	end
684	
685	function BaseCamera:OnCharacterAdded(char)
686		self.resetCameraAngle = self.resetCameraAngle or self:GetEnabled()
687		self.humanoidRootPart = nil
688		if UserInputService.TouchEnabled then
689			self.PlayerGui = player:WaitForChild("PlayerGui")
690			for _, child in ipairs(char:GetChildren()) do
691				if child:IsA("Tool") then
692					self.isAToolEquipped = true
693				end
694			end
695			char.ChildAdded:Connect(function(child)
696				if child:IsA("Tool") then
697					self.isAToolEquipped = true
698				end
699			end)
700			char.ChildRemoved:Connect(function(child)
701				if child:IsA("Tool") then
702					self.isAToolEquipped = false
703				end
704			end)
705		end
706	end
707	
708	function BaseCamera:GetHumanoidRootPart()
709		if not self.humanoidRootPart then
710			if player.Character then
711				local humanoid = player.Character:FindFirstChildOfClass("Humanoid")
712				if humanoid then
713					self.humanoidRootPart = humanoid.RootPart
714				end
715			end
716		end
717		return self.humanoidRootPart
718	end
719	
720	function BaseCamera:GetBodyPartToFollow(humanoid, isDead)
721		-- If the humanoid is dead, prefer the head part if one still exists as a sibling of the humanoid
722		if humanoid:GetState() == Enum.HumanoidStateType.Dead then
723			local character = humanoid.Parent
724			if character and character:IsA("Model") then
725				return character:FindFirstChild("Head") or humanoid.RootPart
726			end
727		end
728	
729		return humanoid.RootPart
730	end
731	
732	function BaseCamera:GetSubjectPosition()
733		local result = self.lastSubjectPosition
734		local camera = game.Workspace.CurrentCamera
735		local cameraSubject = camera and camera.CameraSubject
736	
737		if cameraSubject then
738			if cameraSubject:IsA("Humanoid") then
739				local humanoid = cameraSubject
740				local humanoidIsDead = humanoid:GetState() == Enum.HumanoidStateType.Dead
741	
742				if VRService.VREnabled and humanoidIsDead and humanoid == self.lastSubject then
743					result = self.lastSubjectPosition
744				else
745					local bodyPartToFollow = humanoid.RootPart
746	
747					-- If the humanoid is dead, prefer their head part as a follow target, if it exists
748					if humanoidIsDead then
749						if humanoid.Parent and humanoid.Parent:IsA("Model") then
750							bodyPartToFollow = humanoid.Parent:FindFirstChild("Head") or bodyPartToFollow
751						end
752					end
753	
754					if bodyPartToFollow and bodyPartToFollow:IsA("BasePart") then
755						local heightOffset
756						if humanoid.RigType == Enum.HumanoidRigType.R15 then
757							if humanoid.AutomaticScalingEnabled then
758								heightOffset = R15_HEAD_OFFSET
759								if bodyPartToFollow == humanoid.RootPart then
760									local rootPartSizeOffset = (humanoid.RootPart.Size.Y/2) - (HUMANOID_ROOT_PART_SIZE.Y/2)
761									heightOffset = heightOffset + Vector3.new(0, rootPartSizeOffset, 0)
762								end
763							else
764								heightOffset = R15_HEAD_OFFSET_NO_SCALING
765							end
766						else
767							heightOffset = HEAD_OFFSET
768						end
769	
770						if humanoidIsDead then
771							heightOffset = ZERO_VECTOR3
772						end
773	
774						result = bodyPartToFollow.CFrame.p + bodyPartToFollow.CFrame:vectorToWorldSpace(heightOffset + humanoid.CameraOffset)
775					end
776				end
777	
778			elseif cameraSubject:IsA("VehicleSeat") then
779				local offset = SEAT_OFFSET
780				if VRService.VREnabled then
781					offset = VR_SEAT_OFFSET
782				end
783				result = cameraSubject.CFrame.p + cameraSubject.CFrame:vectorToWorldSpace(offset)
784			elseif cameraSubject:IsA("SkateboardPlatform") then
785				result = cameraSubject.CFrame.p + SEAT_OFFSET
786			elseif cameraSubject:IsA("BasePart") then
787				result = cameraSubject.CFrame.p
788			elseif cameraSubject:IsA("Model") then
789				if cameraSubject.PrimaryPart then
790					result = cameraSubject:GetPrimaryPartCFrame().p
791				else
792					result = cameraSubject:GetModelCFrame().p
793				end
794			end
795		else
796			-- cameraSubject is nil
797			-- Note: Previous RootCamera did not have this else case and let self.lastSubject and self.lastSubjectPosition
798			-- both get set to nil in the case of cameraSubject being nil. This function now exits here to preserve the
799			-- last set valid values for these, as nil values are not handled cases
800			return
801		end
802	
803		self.lastSubject = cameraSubject
804		self.lastSubjectPosition = result
805	
806		return result
807	end
808	
809	function BaseCamera:UpdateDefaultSubjectDistance()
810		if self.portraitMode then
811			self.defaultSubjectDistance = math.clamp(PORTRAIT_DEFAULT_DISTANCE, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
812		else
813			self.defaultSubjectDistance = math.clamp(DEFAULT_DISTANCE, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
814		end
815	end
816	
817	function BaseCamera:OnViewportSizeChanged()
818		local camera = game.Workspace.CurrentCamera
819		local size = camera.ViewportSize
820		self.portraitMode = size.X < size.Y
821		self.isSmallTouchScreen = UserInputService.TouchEnabled and (size.Y < 500 or size.X < 700)
822	
823		self:UpdateDefaultSubjectDistance()
824	end
825	
826	-- Listener for changes to workspace.CurrentCamera
827	function BaseCamera:OnCurrentCameraChanged()
828		if UserInputService.TouchEnabled then
829			if self.viewportSizeChangedConn then
830				self.viewportSizeChangedConn:Disconnect()
831				self.viewportSizeChangedConn = nil
832			end
833	
834			local newCamera = game.Workspace.CurrentCamera
835	
836			if newCamera then
837				self:OnViewportSizeChanged()
838				self.viewportSizeChangedConn = newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
839					self:OnViewportSizeChanged()
840				end)
841			end
842		end
843	
844		-- VR support additions
845		if self.cameraSubjectChangedConn then
846			self.cameraSubjectChangedConn:Disconnect()
847			self.cameraSubjectChangedConn = nil
848		end
849	
850		local camera = game.Workspace.CurrentCamera
851		if camera then
852			self.cameraSubjectChangedConn = camera:GetPropertyChangedSignal("CameraSubject"):Connect(function()
853				self:OnNewCameraSubject()
854			end)
855			self:OnNewCameraSubject()
856		end
857	end
858	
859	function BaseCamera:OnDynamicThumbstickEnabled()
860		if UserInputService.TouchEnabled then
861			self.isDynamicThumbstickEnabled = true
862		end
863	end
864	
865	function BaseCamera:OnDynamicThumbstickDisabled()
866		self.isDynamicThumbstickEnabled = false
867	end
868	
869	function BaseCamera:OnGameSettingsTouchMovementModeChanged()
870		if player.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice then
871			if (UserGameSettings.TouchMovementMode == Enum.TouchMovementMode.DynamicThumbstick
872				or UserGameSettings.TouchMovementMode == Enum.TouchMovementMode.Default) then
873				self:OnDynamicThumbstickEnabled()
874			else
875				self:OnDynamicThumbstickDisabled()
876			end
877		end
878	end
879	
880	function BaseCamera:OnDevTouchMovementModeChanged()
881		if player.DevTouchMovementMode.Name == "DynamicThumbstick" then
882			self:OnDynamicThumbstickEnabled()
883		else
884			self:OnGameSettingsTouchMovementModeChanged()
885		end
886	end
887	
888	function BaseCamera:OnPlayerCameraPropertyChange()
889		-- This call forces re-evaluation of player.CameraMode and clamping to min/max distance which may have changed
890		self:SetCameraToSubjectDistance(self.currentSubjectDistance)
891	end
892	
893	function BaseCamera:GetCameraHeight()
894		if VRService.VREnabled and not self.inFirstPerson then
895			return math.sin(VR_ANGLE) * self.currentSubjectDistance
896		end
897		return 0
898	end
899	
900	function BaseCamera:InputTranslationToCameraAngleChange(translationVector, sensitivity)
901		if not FFlagUserDontAdjustSensitvityForPortrait then
902			local camera = game.Workspace.CurrentCamera
903			if camera and camera.ViewportSize.X > 0 and camera.ViewportSize.Y > 0 and (camera.ViewportSize.Y > camera.ViewportSize.X) then
904				-- Screen has portrait orientation, swap X and Y sensitivity
905				return translationVector * Vector2.new( sensitivity.Y, sensitivity.X)
906			end
907		end
908		return translationVector * sensitivity
909	end
910	
911	function BaseCamera:Enable(enable)
912		if self.enabled ~= enable then
913			self.enabled = enable
914			if self.enabled then
915				self:ConnectInputEvents()
916				self:BindContextActions()
917	
918				if player.CameraMode == Enum.CameraMode.LockFirstPerson then
919					self.currentSubjectDistance = 0.5
920					if not self.inFirstPerson then
921						self:EnterFirstPerson()
922					end
923				end
924			else
925				self:DisconnectInputEvents()
926				self:UnbindContextActions()
927				-- Clean up additional event listeners and reset a bunch of properties
928				self:Cleanup()
929			end
930		end
931	end
932	
933	function BaseCamera:GetEnabled()
934		return self.enabled
935	end
936	
937	function BaseCamera:OnInputBegan(input, processed)
938		if input.UserInputType == Enum.UserInputType.Touch then
939			self:OnTouchBegan(input, processed)
940		elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
941			self:OnMouse2Down(input, processed)
942		elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
943			self:OnMouse3Down(input, processed)
944		end
945	end
946	
947	function BaseCamera:OnInputChanged(input, processed)
948		if input.UserInputType == Enum.UserInputType.Touch then
949			self:OnTouchChanged(input, processed)
950		elseif input.UserInputType == Enum.UserInputType.MouseMovement then
951			self:OnMouseMoved(input, processed)
952		end
953	end
954	
955	function BaseCamera:OnInputEnded(input, processed)
956		if input.UserInputType == Enum.UserInputType.Touch then
957			self:OnTouchEnded(input, processed)
958		elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
959			self:OnMouse2Up(input, processed)
960		elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
961			self:OnMouse3Up(input, processed)
962		end
963	end
964	
965	function BaseCamera:OnPointerAction(wheel, pan, pinch, processed)
966		if processed then
967			return
968		end
969	
970		if pan.Magnitude > 0 then
971			local inversionVector = Vector2.new(1, UserGameSettings:GetCameraYInvertValue())
972			local rotateDelta = self:InputTranslationToCameraAngleChange(PAN_SENSITIVITY*pan, MOUSE_SENSITIVITY)*inversionVector
973			self.rotateInput = self.rotateInput + rotateDelta
974		end
975	
976		local zoom = self.currentSubjectDistance
977		local zoomDelta = -(wheel + pinch)
978	
979		if abs(zoomDelta) > 0 then
980			local newZoom
981			if self.inFirstPerson and zoomDelta > 0 then
982				newZoom = FIRST_PERSON_DISTANCE_THRESHOLD
983			else
984				if FFlagUserFixZoomInZoomOutDiscrepancy then
985					if (zoomDelta > 0) then
986						newZoom = zoom + zoomDelta*(1 + zoom*ZOOM_SENSITIVITY_CURVATURE)
987					else
988						newZoom = (zoom + zoomDelta) / (1 - zoomDelta*ZOOM_SENSITIVITY_CURVATURE)
989					end
990				else
991					newZoom = zoom + zoomDelta*(1 + zoom*ZOOM_SENSITIVITY_CURVATURE)
992				end
993			end
994	
995			self:SetCameraToSubjectDistance(newZoom)
996		end
997	end
998	
999	function BaseCamera:ConnectInputEvents()
1000		self.pointerActionConn = UserInputService.PointerAction:Connect(function(wheel, pan, pinch, processed)
1001			self:OnPointerAction(wheel, pan, pinch, processed)
1002		end)
1003	
1004		self.inputBeganConn = UserInputService.InputBegan:Connect(function(input, processed)
1005			self:OnInputBegan(input, processed)
1006		end)
1007	
1008		self.inputChangedConn = UserInputService.InputChanged:Connect(function(input, processed)
1009			self:OnInputChanged(input, processed)
1010		end)
1011	
1012		self.inputEndedConn = UserInputService.InputEnded:Connect(function(input, processed)
1013			self:OnInputEnded(input, processed)
1014		end)
1015	
1016		self.menuOpenedConn = GuiService.MenuOpened:connect(function()
1017			self:ResetInputStates()
1018		end)
1019	
1020		self.gamepadConnectedConn = UserInputService.GamepadDisconnected:connect(function(gamepadEnum)
1021			if self.activeGamepad ~= gamepadEnum then return end
1022			self.activeGamepad = nil
1023			self:AssignActivateGamepad()
1024		end)
1025	
1026		self.gamepadDisconnectedConn = UserInputService.GamepadConnected:connect(function(gamepadEnum)
1027			if self.activeGamepad == nil then
1028				self:AssignActivateGamepad()
1029			end
1030		end)
1031	
1032		self:AssignActivateGamepad()
1033		if not FFlagUserCameraToggle then
1034			self:UpdateMouseBehavior()
1035		end
1036	end
1037	
1038	function BaseCamera:BindContextActions()
1039		self:BindGamepadInputActions()
1040		self:BindKeyboardInputActions()
1041	end
1042	
1043	function BaseCamera:AssignActivateGamepad()
1044		local connectedGamepads = UserInputService:GetConnectedGamepads()
1045		if #connectedGamepads > 0 then
1046			for i = 1, #connectedGamepads do
1047				if self.activeGamepad == nil then
1048					self.activeGamepad = connectedGamepads[i]
1049				elseif connectedGamepads[i].Value < self.activeGamepad.Value then
1050					self.activeGamepad = connectedGamepads[i]
1051				end
1052			end
1053		end
1054	
1055		if self.activeGamepad == nil then -- nothing is connected, at least set up for gamepad1
1056			self.activeGamepad = Enum.UserInputType.Gamepad1
1057		end
1058	end
1059	
1060	function BaseCamera:DisconnectInputEvents()
1061		if self.inputBeganConn then
1062			self.inputBeganConn:Disconnect()
1063			self.inputBeganConn = nil
1064		end
1065		if self.inputChangedConn then
1066			self.inputChangedConn:Disconnect()
1067			self.inputChangedConn = nil
1068		end
1069		if self.inputEndedConn then
1070			self.inputEndedConn:Disconnect()
1071			self.inputEndedConn = nil
1072		end
1073	end
1074	
1075	function BaseCamera:UnbindContextActions()
1076		for i = 1, #self.boundContextActions do
1077			ContextActionService:UnbindAction(self.boundContextActions[i])
1078		end
1079		self.boundContextActions = {}
1080	end
1081	
1082	function BaseCamera:Cleanup()
1083		if self.pointerActionConn then
1084			self.pointerActionConn:Disconnect()
1085			self.pointerActionConn = nil
1086		end
1087		if self.menuOpenedConn then
1088			self.menuOpenedConn:Disconnect()
1089			self.menuOpenedConn = nil
1090		end
1091		if self.mouseLockToggleConn then
1092			self.mouseLockToggleConn:Disconnect()
1093			self.mouseLockToggleConn = nil
1094		end
1095		if self.gamepadConnectedConn then
1096			self.gamepadConnectedConn:Disconnect()
1097			self.gamepadConnectedConn = nil
1098		end
1099		if self.gamepadDisconnectedConn then
1100			self.gamepadDisconnectedConn:Disconnect()
1101			self.gamepadDisconnectedConn = nil
1102		end
1103		if self.subjectStateChangedConn then
1104			self.subjectStateChangedConn:Disconnect()
1105			self.subjectStateChangedConn = nil
1106		end
1107		if self.viewportSizeChangedConn then
1108			self.viewportSizeChangedConn:Disconnect()
1109			self.viewportSizeChangedConn = nil
1110		end
1111		if self.touchActivateConn then
1112			self.touchActivateConn:Disconnect()
1113			self.touchActivateConn = nil
1114		end
1115	
1116		self.turningLeft = false
1117		self.turningRight = false
1118		self.lastCameraTransform = nil
1119		self.lastSubjectCFrame = nil
1120		self.userPanningTheCamera = false
1121		self.rotateInput = Vector2.new()
1122		self.gamepadPanningCamera = Vector2.new(0,0)
1123	
1124		-- Reset input states
1125		self.startPos = nil
1126		self.lastPos = nil
1127		self.panBeginLook = nil
1128		self.isRightMouseDown = false
1129		self.isMiddleMouseDown = false
1130	
1131		self.fingerTouches = {}
1132		self.dynamicTouchInput = nil
1133		self.numUnsunkTouches = 0
1134	
1135		self.startingDiff = nil
1136		self.pinchBeginZoom = nil
1137	
1138		-- Unlock mouse for example if right mouse button was being held down
1139		if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
1140			UserInputService.MouseBehavior = Enum.MouseBehavior.Default
1141		end
1142	end
1143	
1144	-- This is called when settings menu is opened
1145	function BaseCamera:ResetInputStates()
1146		self.isRightMouseDown = false
1147		self.isMiddleMouseDown = false
1148		self:OnMousePanButtonReleased() -- this function doesn't seem to actually need parameters
1149	
1150		if UserInputService.TouchEnabled then
1151			--[[menu opening was causing serious touch issues
1152			this should disable all active touch events if
1153			they're active when menu opens.]]
1154			for inputObject in pairs(self.fingerTouches) do
1155				self.fingerTouches[inputObject] = nil
1156			end
1157			self.dynamicTouchInput = nil
1158			self.panBeginLook = nil
1159			self.startPos = nil
1160			self.lastPos = nil
1161			self.userPanningTheCamera = false
1162			self.startingDiff = nil
1163			self.pinchBeginZoom = nil
1164			self.numUnsunkTouches = 0
1165		end
1166	end
1167	
1168	function BaseCamera:GetGamepadPan(name, state, input)
1169		if input.UserInputType == self.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
1170	--		if self.L3ButtonDown then
1171	--			-- L3 Thumbstick is depressed, right stick controls dolly in/out
1172	--			if (input.Position.Y > THUMBSTICK_DEADZONE) then
1173	--				self.currentZoomSpeed = 0.96
1174	--			elseif (input.Position.Y < -THUMBSTICK_DEADZONE) then
1175	--				self.currentZoomSpeed = 1.04
1176	--			else
1177	--				self.currentZoomSpeed = 1.00
1178	--			end
1179	--		else
1180				if state == Enum.UserInputState.Cancel then
1181					self.gamepadPanningCamera = ZERO_VECTOR2
1182					return
1183				end
1184	
1185				local inputVector = Vector2.new(input.Position.X, -input.Position.Y)
1186				if inputVector.magnitude > THUMBSTICK_DEADZONE then
1187					self.gamepadPanningCamera = Vector2.new(input.Position.X, -input.Position.Y)
1188				else
1189					self.gamepadPanningCamera = ZERO_VECTOR2
1190				end
1191			--end
1192			return Enum.ContextActionResult.Sink
1193		end
1194		return Enum.ContextActionResult.Pass
1195	end
1196	
1197	function BaseCamera:DoKeyboardPanTurn(name, state, input)
1198		if not self.hasGameLoaded and VRService.VREnabled then
1199			return Enum.ContextActionResult.Pass
1200		end
1201	
1202		if state == Enum.UserInputState.Cancel then
1203			self.turningLeft = false
1204			self.turningRight = false
1205			return Enum.ContextActionResult.Sink
1206		end
1207	
1208		if self.panBeginLook == nil and self.keyPanEnabled then
1209			if input.KeyCode == Enum.KeyCode.Left then
1210				self.turningLeft = state == Enum.UserInputState.Begin
1211			elseif input.KeyCode == Enum.KeyCode.Right then
1212				self.turningRight = state == Enum.UserInputState.Begin
1213			end
1214			return Enum.ContextActionResult.Sink
1215		end
1216		return Enum.ContextActionResult.Pass
1217	end
1218	
1219	function BaseCamera:DoPanRotateCamera(rotateAngle)
1220		local angle = Util.RotateVectorByAngleAndRound(self:GetCameraLookVector() * Vector3.new(1,0,1), rotateAngle, math.pi*0.25)
1221		if angle ~= 0 then
1222			self.rotateInput = self.rotateInput + Vector2.new(angle, 0)
1223			self.lastUserPanCamera = tick()
1224			self.lastCameraTransform = nil
1225		end
1226	end
1227	
1228	function BaseCamera:DoGamepadZoom(name, state, input)
1229		if input.UserInputType == self.activeGamepad then
1230			if input.KeyCode == Enum.KeyCode.ButtonR3 then
1231				if state == Enum.UserInputState.Begin then
1232					if self.distanceChangeEnabled then
1233						local dist = self:GetCameraToSubjectDistance()
1234	
1235						if dist > (GAMEPAD_ZOOM_STEP_2 + GAMEPAD_ZOOM_STEP_3)/2 then
1236							self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_2)
1237						elseif dist > (GAMEPAD_ZOOM_STEP_1 + GAMEPAD_ZOOM_STEP_2)/2 then
1238							self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_1)
1239						else
1240							self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_3)
1241						end
1242					end
1243				end
1244			elseif input.KeyCode == Enum.KeyCode.DPadLeft then
1245				self.dpadLeftDown = (state == Enum.UserInputState.Begin)
1246			elseif input.KeyCode == Enum.KeyCode.DPadRight then
1247				self.dpadRightDown = (state == Enum.UserInputState.Begin)
1248			end
1249	
1250			if self.dpadLeftDown then
1251				self.currentZoomSpeed = 1.04
1252			elseif self.dpadRightDown then
1253				self.currentZoomSpeed = 0.96
1254			else
1255				self.currentZoomSpeed = 1.00
1256			end
1257			return Enum.ContextActionResult.Sink
1258		end
1259		return Enum.ContextActionResult.Pass
1260	--	elseif input.UserInputType == self.activeGamepad and input.KeyCode == Enum.KeyCode.ButtonL3 then
1261	--		if (state == Enum.UserInputState.Begin) then
1262	--			self.L3ButtonDown = true
1263	--		elseif (state == Enum.UserInputState.End) then
1264	--			self.L3ButtonDown = false
1265	--			self.currentZoomSpeed = 1.00
1266	--		end
1267	--	end
1268	end
1269	
1270	function BaseCamera:DoKeyboardZoom(name, state, input)
1271		if not self.hasGameLoaded and VRService.VREnabled then
1272			return Enum.ContextActionResult.Pass
1273		end
1274	
1275		if state ~= Enum.UserInputState.Begin then
1276			return Enum.ContextActionResult.Pass
1277		end
1278	
1279		if self.distanceChangeEnabled and player.CameraMode ~= Enum.CameraMode.LockFirstPerson then
1280			if input.KeyCode == Enum.KeyCode.I then
1281				self:SetCameraToSubjectDistance( self.currentSubjectDistance - 5 )
1282			elseif input.KeyCode == Enum.KeyCode.O then
1283				self:SetCameraToSubjectDistance( self.currentSubjectDistance + 5 )
1284			end
1285			return Enum.ContextActionResult.Sink
1286		end
1287		return Enum.ContextActionResult.Pass
1288	end
1289	
1290	function BaseCamera:BindAction(actionName, actionFunc, createTouchButton, ...)
1291		table.insert(self.boundContextActions, actionName)
1292		ContextActionService:BindActionAtPriority(actionName, actionFunc, createTouchButton,
1293			CAMERA_ACTION_PRIORITY, ...)
1294	end
1295	
1296	function BaseCamera:BindGamepadInputActions()
1297		self:BindAction("BaseCameraGamepadPan", function(name, state, input) return self:GetGamepadPan(name, state, input) end,
1298			false, Enum.KeyCode.Thumbstick2)
1299		self:BindAction("BaseCameraGamepadZoom", function(name, state, input) return self:DoGamepadZoom(name, state, input) end,
1300			false, Enum.KeyCode.DPadLeft, Enum.KeyCode.DPadRight, Enum.KeyCode.ButtonR3)
1301	end
1302	
1303	function BaseCamera:BindKeyboardInputActions()
1304		self:BindAction("BaseCameraKeyboardPanArrowKeys", function(name, state, input) return self:DoKeyboardPanTurn(name, state, input) end,
1305			false, Enum.KeyCode.Left, Enum.KeyCode.Right)
1306		self:BindAction("BaseCameraKeyboardZoom", function(name, state, input) return self:DoKeyboardZoom(name, state, input) end,
1307			false, Enum.KeyCode.I, Enum.KeyCode.O)
1308	end
1309	
1310	local function isInDynamicThumbstickArea(input)
1311		local playerGui = player:FindFirstChildOfClass("PlayerGui")
1312		local touchGui = playerGui and playerGui:FindFirstChild("TouchGui")
1313		local touchFrame = touchGui and touchGui:FindFirstChild("TouchControlFrame")
1314		local thumbstickFrame = touchFrame and touchFrame:FindFirstChild("DynamicThumbstickFrame")
1315	
1316		if not thumbstickFrame then
1317			return false
1318		end
1319	
1320		local frameCornerTopLeft = thumbstickFrame.AbsolutePosition
1321		local frameCornerBottomRight = frameCornerTopLeft + thumbstickFrame.AbsoluteSize
1322		if input.Position.X >= frameCornerTopLeft.X and input.Position.Y >= frameCornerTopLeft.Y then
1323			if input.Position.X <= frameCornerBottomRight.X and input.Position.Y <= frameCornerBottomRight.Y then
1324				return true
1325			end
1326		end
1327	
1328		return false
1329	end
1330	
1331	---Adjusts the camera Y touch Sensitivity when moving away from the center and in the TOUCH_SENSITIVTY_ADJUST_AREA
1332	function BaseCamera:AdjustTouchSensitivity(delta, sensitivity)
1333		local cameraCFrame = game.Workspace.CurrentCamera and game.Workspace.CurrentCamera.CFrame
1334		if not cameraCFrame then
1335			return sensitivity
1336		end
1337		local currPitchAngle = cameraCFrame:ToEulerAnglesYXZ()
1338	
1339		local multiplierY = TOUCH_SENSITIVTY_ADJUST_MAX_Y
1340		if currPitchAngle > TOUCH_ADJUST_AREA_UP and delta.Y < 0 then
1341			local fractionAdjust = (currPitchAngle - TOUCH_ADJUST_AREA_UP)/(MAX_Y - TOUCH_ADJUST_AREA_UP)
1342			fractionAdjust = 1 - (1 - fractionAdjust)^3
1343			multiplierY = TOUCH_SENSITIVTY_ADJUST_MAX_Y - fractionAdjust * (
1344				TOUCH_SENSITIVTY_ADJUST_MAX_Y - TOUCH_SENSITIVTY_ADJUST_MIN_Y)
1345		elseif currPitchAngle < TOUCH_ADJUST_AREA_DOWN and delta.Y > 0 then
1346			local fractionAdjust = (currPitchAngle - TOUCH_ADJUST_AREA_DOWN)/(MIN_Y - TOUCH_ADJUST_AREA_DOWN)
1347			fractionAdjust = 1 - (1 - fractionAdjust)^3
1348			multiplierY = TOUCH_SENSITIVTY_ADJUST_MAX_Y - fractionAdjust * (
1349				TOUCH_SENSITIVTY_ADJUST_MAX_Y - TOUCH_SENSITIVTY_ADJUST_MIN_Y)
1350		end
1351	
1352		return Vector2.new(
1353			sensitivity.X,
1354			sensitivity.Y * multiplierY
1355		)
1356	end
1357	
1358	function BaseCamera:OnTouchBegan(input, processed)
1359		local canUseDynamicTouch = self.isDynamicThumbstickEnabled and not processed
1360		if canUseDynamicTouch then
1361			if self.dynamicTouchInput == nil and isInDynamicThumbstickArea(input) then
1362				-- First input in the dynamic thumbstick area should always be ignored for camera purposes
1363				-- Even if the dynamic thumbstick does not process it immediately
1364				self.dynamicTouchInput = input
1365				return
1366			end
1367			self.fingerTouches[input] = processed
1368			self.inputStartPositions[input] = input.Position
1369			self.inputStartTimes[input] = tick()
1370			self.numUnsunkTouches = self.numUnsunkTouches + 1
1371		end
1372	end
1373	
1374	function BaseCamera:OnTouchChanged(input, processed)
1375		if self.fingerTouches[input] == nil then
1376			if self.isDynamicThumbstickEnabled then
1377				return
1378			end
1379			self.fingerTouches[input] = processed
1380			if not processed then
1381				self.numUnsunkTouches = self.numUnsunkTouches + 1
1382			end
1383		end
1384	
1385		if self.numUnsunkTouches == 1 then
1386			if self.fingerTouches[input] == false then
1387				self.panBeginLook = self.panBeginLook or self:GetCameraLookVector()
1388				self.startPos = self.startPos or input.Position
1389				self.lastPos = self.lastPos or self.startPos
1390				self.userPanningTheCamera = true
1391	
1392				local delta = input.Position - self.lastPos
1393				delta = Vector2.new(delta.X, delta.Y * UserGameSettings:GetCameraYInvertValue())
1394				if self.panEnabled then
1395					local adjustedTouchSensitivity = TOUCH_SENSITIVTY
1396					self:AdjustTouchSensitivity(delta, TOUCH_SENSITIVTY)
1397	
1398					local desiredXYVector = self:InputTranslationToCameraAngleChange(delta, adjustedTouchSensitivity)
1399					self.rotateInput = self.rotateInput + desiredXYVector
1400				end
1401				self.lastPos = input.Position
1402			end
1403		else
1404			self.panBeginLook = nil
1405			self.startPos = nil
1406			self.lastPos = nil
1407			self.userPanningTheCamera = false
1408		end
1409		if self.numUnsunkTouches == 2 then
1410			local unsunkTouches = {}
1411			for touch, wasSunk in pairs(self.fingerTouches) do
1412				if not wasSunk then
1413					table.insert(unsunkTouches, touch)
1414				end
1415			end
1416			if #unsunkTouches == 2 then
1417				local difference = (unsunkTouches[1].Position - unsunkTouches[2].Position).magnitude
1418				if self.startingDiff and self.pinchBeginZoom then
1419					local scale = difference / math.max(0.01, self.startingDiff)
1420					local clampedScale = math.clamp(scale, 0.1, 10)
1421					if self.distanceChangeEnabled then
1422						self:SetCameraToSubjectDistance(self.pinchBeginZoom / clampedScale)
1423					end
1424				else
1425					self.startingDiff = difference
1426					self.pinchBeginZoom = self:GetCameraToSubjectDistance()
1427				end
1428			end
1429		else
1430			self.startingDiff = nil
1431			self.pinchBeginZoom = nil
1432		end
1433	end
1434	
1435	function BaseCamera:OnTouchEnded(input, processed)
1436		if input == self.dynamicTouchInput then
1437			self.dynamicTouchInput = nil
1438			return
1439		end
1440	
1441		if self.fingerTouches[input] == false then
1442			if self.numUnsunkTouches == 1 then
1443				self.panBeginLook = nil
1444				self.startPos = nil
1445				self.lastPos = nil
1446				self.userPanningTheCamera = false
1447			elseif self.numUnsunkTouches == 2 then
1448				self.startingDiff = nil
1449				self.pinchBeginZoom = nil
1450			end
1451		end
1452	
1453		if self.fingerTouches[input] ~= nil and self.fingerTouches[input] == false then
1454			self.numUnsunkTouches = self.numUnsunkTouches - 1
1455		end
1456		self.fingerTouches[input] = nil
1457		self.inputStartPositions[input] = nil
1458		self.inputStartTimes[input] = nil
1459	end
1460	
1461	function BaseCamera:OnMouse2Down(input, processed)
1462		if processed then return end
1463	
1464		self.isRightMouseDown = true
1465		self:OnMousePanButtonPressed(input, processed)
1466	end
1467	
1468	function BaseCamera:OnMouse2Up(input, processed)
1469		self.isRightMouseDown = false
1470		self:OnMousePanButtonReleased(input, processed)
1471	end
1472	
1473	function BaseCamera:OnMouse3Down(input, processed)
1474		if processed then return end
1475	
1476		self.isMiddleMouseDown = true
1477		self:OnMousePanButtonPressed(input, processed)
1478	end
1479	
1480	function BaseCamera:OnMouse3Up(input, processed)
1481		self.isMiddleMouseDown = false
1482		self:OnMousePanButtonReleased(input, processed)
1483	end
1484	
1485	function BaseCamera:OnMouseMoved(input, processed)
1486		if not self.hasGameLoaded and VRService.VREnabled then
1487			return
1488		end
1489	
1490		local inputDelta = input.Delta
1491		inputDelta = Vector2.new(inputDelta.X, inputDelta.Y * UserGameSettings:GetCameraYInvertValue())
1492	
1493		local isInputPanning = FFlagUserCameraToggle and CameraInput.getPanning()
1494		local isBeginLook = self.startPos and self.lastPos and self.panBeginLook
1495		local isPanning = isBeginLook or self.inFirstPerson or self.inMouseLockedMode or isInputPanning
1496	
1497		if self.panEnabled and isPanning then
1498			local desiredXYVector = self:InputTranslationToCameraAngleChange(inputDelta, MOUSE_SENSITIVITY)
1499			self.rotateInput = self.rotateInput + desiredXYVector
1500		end
1501	
1502		if self.startPos and self.lastPos and self.panBeginLook then
1503			self.lastPos = self.lastPos + input.Delta
1504		end
1505	end
1506	
1507	function BaseCamera:OnMousePanButtonPressed(input, processed)
1508		if processed then return end
1509		if not FFlagUserCameraToggle then
1510			self:UpdateMouseBehavior()
1511		end
1512		self.panBeginLook = self.panBeginLook or self:GetCameraLookVector()
1513		self.startPos = self.startPos or input.Position
1514		self.lastPos = self.lastPos or self.startPos
1515		self.userPanningTheCamera = true
1516	end
1517	
1518	function BaseCamera:OnMousePanButtonReleased(input, processed)
1519		if not FFlagUserCameraToggle then
1520			self:UpdateMouseBehavior()
1521		end
1522		if not (self.isRightMouseDown or self.isMiddleMouseDown) then
1523			self.panBeginLook = nil
1524			self.startPos = nil
1525			self.lastPos = nil
1526			self.userPanningTheCamera = false
1527		end
1528	end
1529	
1530	function BaseCamera:UpdateMouseBehavior()
1531		if FFlagUserCameraToggle and self.isCameraToggle then
1532			CameraUI.setCameraModeToastEnabled(true)
1533			CameraInput.enableCameraToggleInput()
1534			CameraToggleStateController(self.inFirstPerson)
1535		else
1536			if FFlagUserCameraToggle then
1537				CameraUI.setCameraModeToastEnabled(false)
1538				CameraInput.disableCameraToggleInput()
1539			end
1540			-- first time transition to first person mode or mouse-locked third person
1541			if self.inFirstPerson or self.inMouseLockedMode then
1542				--UserGameSettings.RotationType = Enum.RotationType.CameraRelative
1543				UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
1544			else
1545				UserGameSettings.RotationType = Enum.RotationType.MovementRelative
1546				if self.isRightMouseDown or self.isMiddleMouseDown then
1547					UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
1548				else
1549					UserInputService.MouseBehavior = Enum.MouseBehavior.Default
1550				end
1551			end
1552		end
1553	end
1554	
1555	function BaseCamera:UpdateForDistancePropertyChange()
1556		-- Calling this setter with the current value will force checking that it is still
1557		-- in range after a change to the min/max distance limits
1558		self:SetCameraToSubjectDistance(self.currentSubjectDistance)
1559	end
1560	
1561	function BaseCamera:SetCameraToSubjectDistance(desiredSubjectDistance)
1562		local lastSubjectDistance = self.currentSubjectDistance
1563	
1564		-- By default, camera modules will respect LockFirstPerson and override the currentSubjectDistance with 0
1565		-- regardless of what Player.CameraMinZoomDistance is set to, so that first person can be made
1566		-- available by the developer without needing to allow players to mousewheel dolly into first person.
1567		-- Some modules will override this function to remove or change first-person capability.
1568		if player.CameraMode == Enum.CameraMode.LockFirstPerson then
1569			self.currentSubjectDistance = 0.5
1570			if not self.inFirstPerson then
1571				self:EnterFirstPerson()
1572			end
1573		else
1574			local newSubjectDistance = math.clamp(desiredSubjectDistance, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
1575			if newSubjectDistance < FIRST_PERSON_DISTANCE_THRESHOLD then
1576				self.currentSubjectDistance = 0.5
1577				if not self.inFirstPerson then
1578					self:EnterFirstPerson()
1579				end
1580			else
1581				self.currentSubjectDistance = newSubjectDistance
1582				if self.inFirstPerson then
1583					self:LeaveFirstPerson()
1584				end
1585			end
1586		end
1587	
1588		-- Pass target distance and zoom direction to the zoom controller
1589		ZoomController.SetZoomParameters(self.currentSubjectDistance, math.sign(desiredSubjectDistance - lastSubjectDistance))
1590	
1591		-- Returned only for convenience to the caller to know the outcome
1592		return self.currentSubjectDistance
1593	end
1594	
1595	function BaseCamera:SetCameraType( cameraType )
1596		--Used by derived classes
1597		self.cameraType = cameraType
1598	end
1599	
1600	function BaseCamera:GetCameraType()
1601		return self.cameraType
1602	end
1603	
1604	-- Movement mode standardized to Enum.ComputerCameraMovementMode values
1605	function BaseCamera:SetCameraMovementMode( cameraMovementMode )
1606		self.cameraMovementMode = cameraMovementMode
1607	end
1608	
1609	function BaseCamera:GetCameraMovementMode()
1610		return self.cameraMovementMode
1611	end
1612	
1613	function BaseCamera:SetIsMouseLocked(mouseLocked)
1614		self.inMouseLockedMode = mouseLocked
1615		if not FFlagUserCameraToggle then
1616			self:UpdateMouseBehavior()
1617		end
1618	end
1619	
1620	function BaseCamera:GetIsMouseLocked()
1621		return self.inMouseLockedMode
1622	end
1623	
1624	function BaseCamera:SetMouseLockOffset(offsetVector)
1625		self.mouseLockOffset = offsetVector
1626	end
1627	
1628	function BaseCamera:GetMouseLockOffset()
1629		return self.mouseLockOffset
1630	end
1631	
1632	function BaseCamera:InFirstPerson()
1633		return self.inFirstPerson
1634	end
1635	
1636	function BaseCamera:EnterFirstPerson()
1637		-- Overridden in ClassicCamera, the only module which supports FirstPerson
1638	end
1639	
1640	function BaseCamera:LeaveFirstPerson()
1641		-- Overridden in ClassicCamera, the only module which supports FirstPerson
1642	end
1643	
1644	-- Nominal distance, set by dollying in and out with the mouse wheel or equivalent, not measured distance
1645	function BaseCamera:GetCameraToSubjectDistance()
1646		return self.currentSubjectDistance
1647	end
1648	
1649	-- Actual measured distance to the camera Focus point, which may be needed in special circumstances, but should
1650	-- never be used as the starting point for updating the nominal camera-to-subject distance (self.currentSubjectDistance)
1651	-- since that is a desired target value set only by mouse wheel (or equivalent) input, PopperCam, and clamped to min max camera distance
1652	function BaseCamera:GetMeasuredDistanceToFocus()
1653		local camera = game.Workspace.CurrentCamera
1654		if camera then
1655			return (camera.CoordinateFrame.p - camera.Focus.p).magnitude
1656		end
1657		return nil
1658	end
1659	
1660	function BaseCamera:GetCameraLookVector()
1661		return game.Workspace.CurrentCamera and game.Workspace.CurrentCamera.CFrame.lookVector or UNIT_Z
1662	end
1663	
1664	-- Replacements for RootCamera:RotateCamera() which did not actually rotate the camera
1665	-- suppliedLookVector is not normally passed in, it's used only by Watch camera
1666	function BaseCamera:CalculateNewLookCFrame(suppliedLookVector)
1667		local currLookVector = suppliedLookVector or self:GetCameraLookVector()
1668		local currPitchAngle = math.asin(currLookVector.y)
1669		local yTheta = math.clamp(self.rotateInput.y, -MAX_Y + currPitchAngle, -MIN_Y + currPitchAngle)
1670		local constrainedRotateInput = Vector2.new(self.rotateInput.x, yTheta)
1671		local startCFrame = CFrame.new(ZERO_VECTOR3, currLookVector)
1672		local newLookCFrame = CFrame.Angles(0, -constrainedRotateInput.x, 0) * startCFrame * CFrame.Angles(-constrainedRotateInput.y,0,0)
1673		return newLookCFrame
1674	end
1675	function BaseCamera:CalculateNewLookVector(suppliedLookVector)
1676		local newLookCFrame = self:CalculateNewLookCFrame(suppliedLookVector)
1677		return newLookCFrame.lookVector
1678	end
1679	
1680	function BaseCamera:CalculateNewLookVectorVR()
1681		local subjectPosition = self:GetSubjectPosition()
1682		local vecToSubject = (subjectPosition - game.Workspace.CurrentCamera.CFrame.p)
1683		local currLookVector = (vecToSubject * X1_Y0_Z1).unit
1684		local vrRotateInput = Vector2.new(self.rotateInput.x, 0)
1685		local startCFrame = CFrame.new(ZERO_VECTOR3, currLookVector)
1686		local yawRotatedVector = (CFrame.Angles(0, -vrRotateInput.x, 0) * startCFrame * CFrame.Angles(-vrRotateInput.y,0,0)).lookVector
1687		return (yawRotatedVector * X1_Y0_Z1).unit
1688	end
1689	
1690	function BaseCamera:GetHumanoid()
1691		local character = player and player.Character
1692		if character then
1693			local resultHumanoid = self.humanoidCache[player]
1694			if resultHumanoid and resultHumanoid.Parent == character then
1695				return resultHumanoid
1696			else
1697				self.humanoidCache[player] = nil -- Bust Old Cache
1698				local humanoid = character:FindFirstChildOfClass("Humanoid")
1699				if humanoid then
1700					self.humanoidCache[player] = humanoid
1701				end
1702				return humanoid
1703			end
1704		end
1705		return nil
1706	end
1707	
1708	function BaseCamera:GetHumanoidPartToFollow(humanoid, humanoidStateType)
1709		if humanoidStateType == Enum.HumanoidStateType.Dead then
1710			local character = humanoid.Parent
1711			if character then
1712				return character:FindFirstChild("Head") or humanoid.Torso
1713			else
1714				return humanoid.Torso
1715			end
1716		else
1717			return humanoid.Torso
1718		end
1719	end
1720	
1721	function BaseCamera:UpdateGamepad()
1722		local gamepadPan = self.gamepadPanningCamera
1723		if gamepadPan and (self.hasGameLoaded or not VRService.VREnabled) then
1724			gamepadPan = Util.GamepadLinearToCurve(gamepadPan)
1725			local currentTime = tick()
1726			if gamepadPan.X ~= 0 or gamepadPan.Y ~= 0 then
1727				self.userPanningTheCamera = true
1728			elseif gamepadPan == ZERO_VECTOR2 then
1729				self.lastThumbstickRotate = nil
1730				if self.lastThumbstickPos == ZERO_VECTOR2 then
1731					self.currentSpeed = 0
1732				end
1733			end
1734	
1735			local finalConstant = 0
1736	
1737			if self.lastThumbstickRotate then
1738				if VRService.VREnabled then
1739					self.currentSpeed = self.vrMaxSpeed
1740				else
1741					local elapsedTime = (currentTime - self.lastThumbstickRotate) * 10
1742					self.currentSpeed = self.currentSpeed + (self.maxSpeed * ((elapsedTime*elapsedTime)/self.numOfSeconds))
1743	
1744					if self.currentSpeed > self.maxSpeed then self.currentSpeed = self.maxSpeed end
1745	
1746					if self.lastVelocity then
1747						local velocity = (gamepadPan - self.lastThumbstickPos)/(currentTime - self.lastThumbstickRotate)
1748						local velocityDeltaMag = (velocity - self.lastVelocity).magnitude
1749	
1750						if velocityDeltaMag > 12 then
1751							self.currentSpeed = self.currentSpeed * (20/velocityDeltaMag)
1752							if self.currentSpeed > self.maxSpeed then self.currentSpeed = self.maxSpeed end
1753						end
1754					end
1755				end
1756	
1757				finalConstant = UserGameSettings.GamepadCameraSensitivity * self.currentSpeed
1758				self.lastVelocity = (gamepadPan - self.lastThumbstickPos)/(currentTime - self.lastThumbstickRotate)
1759			end
1760	
1761			self.lastThumbstickPos = gamepadPan
1762			self.lastThumbstickRotate = currentTime
1763	
1764			return Vector2.new( gamepadPan.X * finalConstant, gamepadPan.Y * finalConstant * self.ySensitivity * UserGameSettings:GetCameraYInvertValue())
1765		end
1766	
1767		return ZERO_VECTOR2
1768	end
1769	
1770	-- [[ VR Support Section ]] --
1771	
1772	function BaseCamera:ApplyVRTransform()
1773		if not VRService.VREnabled then
1774			return
1775		end
1776	
1777		--we only want this to happen in first person VR
1778		local rootJoint = self.humanoidRootPart and self.humanoidRootPart:FindFirstChild("RootJoint")
1779		if not rootJoint then
1780			return
1781		end
1782	
1783		local cameraSubject = game.Workspace.CurrentCamera.CameraSubject
1784		local isInVehicle = cameraSubject and cameraSubject:IsA("VehicleSeat")
1785	
1786		if self.inFirstPerson and not isInVehicle then
1787			local vrFrame = VRService:GetUserCFrame(Enum.UserCFrame.Head)
1788			local vrRotation = vrFrame - vrFrame.p
1789			rootJoint.C0 = CFrame.new(vrRotation:vectorToObjectSpace(vrFrame.p)) * CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
1790		else
1791			rootJoint.C0 = CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
1792		end
1793	end
1794	
1795	function BaseCamera:IsInFirstPerson()
1796		return self.inFirstPerson
1797	end
1798	
1799	function BaseCamera:ShouldUseVRRotation()
1800		if not VRService.VREnabled then
1801			return false
1802		end
1803	
1804		if not self.VRRotationIntensityAvailable and tick() - self.lastVRRotationIntensityCheckTime < 1 then
1805			return false
1806		end
1807	
1808		local success, vrRotationIntensity = pcall(function() return StarterGui:GetCore("VRRotationIntensity") end)
1809		self.VRRotationIntensityAvailable = success and vrRotationIntensity ~= nil
1810		self.lastVRRotationIntensityCheckTime = tick()
1811	
1812		self.shouldUseVRRotation = success and vrRotationIntensity ~= nil and vrRotationIntensity ~= "Smooth"
1813	
1814		return self.shouldUseVRRotation
1815	end
1816	
1817	function BaseCamera:GetVRRotationInput()
1818		local vrRotateSum = ZERO_VECTOR2
1819		local success, vrRotationIntensity = pcall(function() return StarterGui:GetCore("VRRotationIntensity") end)
1820	
1821		if not success then
1822			return
1823		end
1824	
1825		local vrGamepadRotation = self.GamepadPanningCamera or ZERO_VECTOR2
1826		local delayExpired = (tick() - self.lastVRRotationTime) >= self:GetRepeatDelayValue(vrRotationIntensity)
1827	
1828		if math.abs(vrGamepadRotation.x) >= self:GetActivateValue() then
1829			if (delayExpired or not self.vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2]) then
1830				local sign = 1
1831				if vrGamepadRotation.x < 0 then
1832					sign = -1
1833				end
1834				vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity) * sign
1835				self.vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = true
1836			end
1837		elseif math.abs(vrGamepadRotation.x) < self:GetActivateValue() - 0.1 then
1838			self.vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = nil
1839		end
1840		if self.turningLeft then
1841			if delayExpired or not self.vrRotateKeyCooldown[Enum.KeyCode.Left] then
1842				vrRotateSum = vrRotateSum - self:GetRotateAmountValue(vrRotationIntensity)
1843				self.vrRotateKeyCooldown[Enum.KeyCode.Left] = true
1844			end
1845		else
1846			self.vrRotateKeyCooldown[Enum.KeyCode.Left] = nil
1847		end
1848		if self.turningRight then
1849			if (delayExpired or not self.vrRotateKeyCooldown[Enum.KeyCode.Right]) then
1850				vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity)
1851				self.vrRotateKeyCooldown[Enum.KeyCode.Right] = true
1852			end
1853		else
1854			self.vrRotateKeyCooldown[Enum.KeyCode.Right] = nil
1855		end
1856	
1857		if vrRotateSum ~= ZERO_VECTOR2 then
1858			self.lastVRRotationTime = tick()
1859		end
1860	
1861		return vrRotateSum
1862	end
1863	
1864	function BaseCamera:CancelCameraFreeze(keepConstraints)
1865		if not keepConstraints then
1866			self.cameraTranslationConstraints = Vector3.new(self.cameraTranslationConstraints.x, 1, self.cameraTranslationConstraints.z)
1867		end
1868		if self.cameraFrozen then
1869			self.trackingHumanoid = nil
1870			self.cameraFrozen = false
1871		end
1872	end
1873	
1874	function BaseCamera:StartCameraFreeze(subjectPosition, humanoidToTrack)
1875		if not self.cameraFrozen then
1876			self.humanoidJumpOrigin = subjectPosition
1877			self.trackingHumanoid = humanoidToTrack
1878			self.cameraTranslationConstraints = Vector3.new(self.cameraTranslationConstraints.x, 0, self.cameraTranslationConstraints.z)
1879			self.cameraFrozen = true
1880		end
1881	end
1882	
1883	function BaseCamera:OnNewCameraSubject()
1884		if self.subjectStateChangedConn then
1885			self.subjectStateChangedConn:Disconnect()
1886			self.subjectStateChangedConn = nil
1887		end
1888	
1889		local humanoid = workspace.CurrentCamera and workspace.CurrentCamera.CameraSubject
1890		if self.trackingHumanoid ~= humanoid then
1891			self:CancelCameraFreeze()
1892		end
1893		if humanoid and humanoid:IsA("Humanoid") then
1894			self.subjectStateChangedConn = humanoid.StateChanged:Connect(function(oldState, newState)
1895				if VRService.VREnabled and newState == Enum.HumanoidStateType.Jumping and not self.inFirstPerson then
1896					self:StartCameraFreeze(self:GetSubjectPosition(), humanoid)
1897				elseif newState ~= Enum.HumanoidStateType.Jumping and newState ~= Enum.HumanoidStateType.Freefall then
1898					self:CancelCameraFreeze(true)
1899				end
1900			end)
1901		end
1902	end
1903	
1904	function BaseCamera:GetVRFocus(subjectPosition, timeDelta)
1905		local lastFocus = self.LastCameraFocus or subjectPosition
1906		if not self.cameraFrozen then
1907			self.cameraTranslationConstraints = Vector3.new(self.cameraTranslationConstraints.x, math.min(1, self.cameraTranslationConstraints.y + 0.42 * timeDelta), self.cameraTranslationConstraints.z)
1908		end
1909	
1910		local newFocus
1911		if self.cameraFrozen and self.humanoidJumpOrigin and self.humanoidJumpOrigin.y > lastFocus.y then
1912			newFocus = CFrame.new(Vector3.new(subjectPosition.x, math.min(self.humanoidJumpOrigin.y, lastFocus.y + 5 * timeDelta), subjectPosition.z))
1913		else
1914			newFocus = CFrame.new(Vector3.new(subjectPosition.x, lastFocus.y, subjectPosition.z):lerp(subjectPosition, self.cameraTranslationConstraints.y))
1915		end
1916	
1917		if self.cameraFrozen then
1918			-- No longer in 3rd person
1919			if self.inFirstPerson then -- not VRService.VREnabled
1920				self:CancelCameraFreeze()
1921			end
1922			-- This case you jumped off a cliff and want to keep your character in view
1923			-- 0.5 is to fix floating point error when not jumping off cliffs
1924			if self.humanoidJumpOrigin and subjectPosition.y < (self.humanoidJumpOrigin.y - 0.5) then
1925				self:CancelCameraFreeze()
1926			end
1927		end
1928	
1929		return newFocus
1930	end
1931	
1932	function BaseCamera:GetRotateAmountValue(vrRotationIntensity)
1933		vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
1934		if vrRotationIntensity then
1935			if vrRotationIntensity == "Low" then
1936				return VR_LOW_INTENSITY_ROTATION
1937			elseif vrRotationIntensity == "High" then
1938				return VR_HIGH_INTENSITY_ROTATION
1939			end
1940		end
1941		return ZERO_VECTOR2
1942	end
1943	
1944	function BaseCamera:GetRepeatDelayValue(vrRotationIntensity)
1945		vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
1946		if vrRotationIntensity then
1947			if vrRotationIntensity == "Low" then
1948				return VR_LOW_INTENSITY_REPEAT
1949			elseif vrRotationIntensity == "High" then
1950				return VR_HIGH_INTENSITY_REPEAT
1951			end
1952		end
1953		return 0
1954	end
1955	
1956	function BaseCamera:Update(dt)
1957		error("BaseCamera:Update() This is a virtual function that should never be getting called.", 2)
1958	end
1959	
1960	BaseCamera.UpCFrame = CFrame.new()
1961	
1962	function BaseCamera:UpdateUpCFrame(cf)
1963		self.UpCFrame = cf
1964	end
1965	local ZERO = Vector3.new(0, 0, 0)
1966	function BaseCamera:CalculateNewLookCFrame(suppliedLookVector)
1967		local currLookVector = suppliedLookVector or self:GetCameraLookVector()
1968		currLookVector = self.UpCFrame:VectorToObjectSpace(currLookVector)
1969		
1970		local currPitchAngle = math.asin(currLookVector.y)
1971		local yTheta = math.clamp(self.rotateInput.y, -MAX_Y + currPitchAngle, -MIN_Y + currPitchAngle)
1972		local constrainedRotateInput = Vector2.new(self.rotateInput.x, yTheta)
1973		local startCFrame = CFrame.new(ZERO, currLookVector)
1974		local newLookCFrame = CFrame.Angles(0, -constrainedRotateInput.x, 0) * startCFrame * CFrame.Angles(-constrainedRotateInput.y,0,0)
1975		
1976		return newLookCFrame
1977	end
1978	
1979	return BaseCamera
1980end
1981
1982function _BaseOcclusion()
1983	--[[ The Module ]]--
1984	local BaseOcclusion = {}
1985	BaseOcclusion.__index = BaseOcclusion
1986	setmetatable(BaseOcclusion, {
1987		__call = function(_, ...)
1988			return BaseOcclusion.new(...)
1989		end
1990	})
1991	
1992	function BaseOcclusion.new()
1993		local self = setmetatable({}, BaseOcclusion)
1994		return self
1995	end
1996	
1997	-- Called when character is added
1998	function BaseOcclusion:CharacterAdded(char, player)
1999	end
2000	
2001	-- Called when character is about to be removed
2002	function BaseOcclusion:CharacterRemoving(char, player)
2003	end
2004	
2005	function BaseOcclusion:OnCameraSubjectChanged(newSubject)
2006	end
2007	
2008	--[[ Derived classes are required to override and implement all of the following functions ]]--
2009	function BaseOcclusion:GetOcclusionMode()
2010		-- Must be overridden in derived classes to return an Enum.DevCameraOcclusionMode value
2011		warn("BaseOcclusion GetOcclusionMode must be overridden by derived classes")
2012		return nil
2013	end
2014	
2015	function BaseOcclusion:Enable(enabled)
2016		warn("BaseOcclusion Enable must be overridden by derived classes")
2017	end
2018	
2019	function BaseOcclusion:Update(dt, desiredCameraCFrame, desiredCameraFocus)
2020		warn("BaseOcclusion Update must be overridden by derived classes")
2021		return desiredCameraCFrame, desiredCameraFocus
2022	end
2023	
2024	return BaseOcclusion
2025end
2026
2027function _Popper()
2028	
2029	local Players = game:GetService("Players")
2030	
2031	local camera = game.Workspace.CurrentCamera
2032	
2033	local min = math.min
2034	local tan = math.tan
2035	local rad = math.rad
2036	local inf = math.huge
2037	local ray = Ray.new
2038	
2039	local function getTotalTransparency(part)
2040		return 1 - (1 - part.Transparency)*(1 - part.LocalTransparencyModifier)
2041	end
2042	
2043	local function eraseFromEnd(t, toSize)
2044		for i = #t, toSize + 1, -1 do
2045			t[i] = nil
2046		end
2047	end
2048	
2049	local nearPlaneZ, projX, projY do
2050		local function updateProjection()
2051			local fov = rad(camera.FieldOfView)
2052			local view = camera.ViewportSize
2053			local ar = view.X/view.Y
2054	
2055			projY = 2*tan(fov/2)
2056			projX = ar*projY
2057		end
2058	
2059		camera:GetPropertyChangedSignal("FieldOfView"):Connect(updateProjection)
2060		camera:GetPropertyChangedSignal("ViewportSize"):Connect(updateProjection)
2061	
2062		updateProjection()
2063	
2064		nearPlaneZ = camera.NearPlaneZ
2065		camera:GetPropertyChangedSignal("NearPlaneZ"):Connect(function()
2066			nearPlaneZ = camera.NearPlaneZ
2067		end)
2068	end
2069	
2070	local blacklist = {} do
2071		local charMap = {}
2072	
2073		local function refreshIgnoreList()
2074			local n = 1
2075			blacklist = {}
2076			for _, character in pairs(charMap) do
2077				blacklist[n] = character
2078				n = n + 1
2079			end
2080		end
2081	
2082		local function playerAdded(player)
2083			local function characterAdded(character)
2084				charMap[player] = character
2085				refreshIgnoreList()
2086			end
2087			local function characterRemoving()
2088				charMap[player] = nil
2089				refreshIgnoreList()
2090			end
2091	
2092			player.CharacterAdded:Connect(characterAdded)
2093			player.CharacterRemoving:Connect(characterRemoving)
2094			if player.Character then
2095				characterAdded(player.Character)
2096			end
2097		end
2098	
2099		local function playerRemoving(player)
2100			charMap[player] = nil
2101			refreshIgnoreList()
2102		end
2103	
2104		Players.PlayerAdded:Connect(playerAdded)
2105		Players.PlayerRemoving:Connect(playerRemoving)
2106	
2107		for _, player in ipairs(Players:GetPlayers()) do
2108			playerAdded(player)
2109		end
2110		refreshIgnoreList()
2111	end
2112	
2113	--------------------------------------------------------------------------------------------
2114	-- Popper uses the level geometry find an upper bound on subject-to-camera distance.
2115	--
2116	-- Hard limits are applied immediately and unconditionally. They are generally caused
2117	-- when level geometry intersects with the near plane (with exceptions, see below).
2118	--
2119	-- Soft limits are only applied under certain conditions.
2120	-- They are caused when level geometry occludes the subject without actually intersecting
2121	-- with the near plane at the target distance.
2122	--
2123	-- Soft limits can be promoted to hard limits and hard limits can be demoted to soft limits.
2124	-- We usually don"t want the latter to happen.
2125	--
2126	-- A soft limit will be promoted to a hard limit if an obstruction
2127	-- lies between the current and target camera positions.
2128	--------------------------------------------------------------------------------------------
2129	
2130	local subjectRoot
2131	local subjectPart
2132	
2133	camera:GetPropertyChangedSignal("CameraSubject"):Connect(function()
2134		local subject = camera.CameraSubject
2135		if subject:IsA("Humanoid") then
2136			subjectPart = subject.RootPart
2137		elseif subject:IsA("BasePart") then
2138			subjectPart = subject
2139		else
2140			subjectPart = nil
2141		end
2142	end)
2143	
2144	local function canOcclude(part)
2145		-- Occluders must be:
2146		-- 1. Opaque
2147		-- 2. Interactable
2148		-- 3. Not in the same assembly as the subject
2149	
2150		return
2151			getTotalTransparency(part) < 0.25 and
2152			part.CanCollide and
2153			subjectRoot ~= (part:GetRootPart() or part) and
2154			not part:IsA("TrussPart")
2155	end
2156	
2157	-- Offsets for the volume visibility test
2158	local SCAN_SAMPLE_OFFSETS = {
2159		Vector2.new( 0.4, 0.0),
2160		Vector2.new(-0.4, 0.0),
2161		Vector2.new( 0.0,-0.4),
2162		Vector2.new( 0.0, 0.4),
2163		Vector2.new( 0.0, 0.2),
2164	}
2165	
2166	--------------------------------------------------------------------------------
2167	-- Piercing raycasts
2168	
2169	local function getCollisionPoint(origin, dir)
2170		local originalSize = #blacklist
2171		repeat
2172			local hitPart, hitPoint = workspace:FindPartOnRayWithIgnoreList(
2173				ray(origin, dir), blacklist, false, true
2174			)
2175	
2176			if hitPart then
2177				if hitPart.CanCollide then
2178					eraseFromEnd(blacklist, originalSize)
2179					return hitPoint, true
2180				end
2181				blacklist[#blacklist + 1] = hitPart
2182			end
2183		until not hitPart
2184	
2185		eraseFromEnd(blacklist, originalSize)
2186		return origin + dir, false
2187	end
2188	
2189	--------------------------------------------------------------------------------
2190	
2191	local function queryPoint(origin, unitDir, dist, lastPos)
2192		debug.profilebegin("queryPoint")
2193	
2194		local originalSize = #blacklist
2195	
2196		dist = dist + nearPlaneZ
2197		local target = origin + unitDir*dist
2198	
2199		local softLimit = inf
2200		local hardLimit = inf
2201		local movingOrigin = origin
2202	
2203		repeat
2204			local entryPart, entryPos = workspace:FindPartOnRayWithIgnoreList(ray(movingOrigin, target - movingOrigin), blacklist, false, true)
2205	
2206			if entryPart then
2207				if canOcclude(entryPart) then
2208					local wl = {entryPart}
2209					local exitPart = workspace:FindPartOnRayWithWhitelist(ray(target, entryPos - target), wl, true)
2210	
2211					local lim = (entryPos - origin).Magnitude
2212	
2213					if exitPart then
2214						local promote = false
2215						if lastPos then
2216							promote =
2217								workspace:FindPartOnRayWithWhitelist(ray(lastPos, target - lastPos), wl, true) or
2218								workspace:FindPartOnRayWithWhitelist(ray(target, lastPos - target), wl, true)
2219						end
2220	
2221						if promote then
2222							-- Ostensibly a soft limit, but the camera has passed through it in the last frame, so promote to a hard limit.
2223							hardLimit = lim
2224						elseif dist < softLimit then
2225							-- Trivial soft limit
2226							softLimit = lim
2227						end
2228					else
2229						-- Trivial hard limit
2230						hardLimit = lim
2231					end
2232				end
2233	
2234				blacklist[#blacklist + 1] = entryPart
2235				movingOrigin = entryPos - unitDir*1e-3
2236			end
2237		until hardLimit < inf or not entryPart
2238	
2239		eraseFromEnd(blacklist, originalSize)
2240	
2241		debug.profileend()
2242		return softLimit - nearPlaneZ, hardLimit - nearPlaneZ
2243	end
2244	
2245	local function queryViewport(focus, dist)
2246		debug.profilebegin("queryViewport")
2247	
2248		local fP =  focus.p
2249		local fX =  focus.rightVector
2250		local fY =  focus.upVector
2251		local fZ = -focus.lookVector
2252	
2253		local viewport = camera.ViewportSize
2254	
2255		local hardBoxLimit = inf
2256		local softBoxLimit = inf
2257	
2258		-- Center the viewport on the PoI, sweep points on the edge towards the target, and take the minimum limits
2259		for viewX = 0, 1 do
2260			local worldX = fX*((viewX - 0.5)*projX)
2261	
2262			for viewY = 0, 1 do
2263				local worldY = fY*((viewY - 0.5)*projY)
2264	
2265				local origin = fP + nearPlaneZ*(worldX + worldY)
2266				local lastPos = camera:ViewportPointToRay(
2267					viewport.x*viewX,
2268					viewport.y*viewY
2269				).Origin
2270	
2271				local softPointLimit, hardPointLimit = queryPoint(origin, fZ, dist, lastPos)
2272	
2273				if hardPointLimit < hardBoxLimit then
2274					hardBoxLimit = hardPointLimit
2275				end
2276				if softPointLimit < softBoxLimit then
2277					softBoxLimit = softPointLimit
2278				end
2279			end
2280		end
2281		debug.profileend()
2282	
2283		return softBoxLimit, hardBoxLimit
2284	end
2285	
2286	local function testPromotion(focus, dist, focusExtrapolation)
2287		debug.profilebegin("testPromotion")
2288	
2289		local fP = focus.p
2290		local fX = focus.rightVector
2291		local fY = focus.upVector
2292		local fZ = -focus.lookVector
2293	
2294		do
2295			-- Dead reckoning the camera rotation and focus
2296			debug.profilebegin("extrapolate")
2297	
2298			local SAMPLE_DT = 0.0625
2299			local SAMPLE_MAX_T = 1.25
2300	
2301			local maxDist = (getCollisionPoint(fP, focusExtrapolation.posVelocity*SAMPLE_MAX_T) - fP).Magnitude
2302			-- Metric that decides how many samples to take
2303			local combinedSpeed = focusExtrapolation.posVelocity.magnitude
2304	
2305			for dt = 0, min(SAMPLE_MAX_T, focusExtrapolation.rotVelocity.magnitude + maxDist/combinedSpeed), SAMPLE_DT do
2306				local cfDt = focusExtrapolation.extrapolate(dt) -- Extrapolated CFrame at time dt
2307	
2308				if queryPoint(cfDt.p, -cfDt.lookVector, dist) >= dist then
2309					return false
2310				end
2311			end
2312	
2313			debug.profileend()
2314		end
2315	
2316		do
2317			-- Test screen-space offsets from the focus for the presence of soft limits
2318			debug.profilebegin("testOffsets")
2319	
2320			for _, offset in ipairs(SCAN_SAMPLE_OFFSETS) do
2321				local scaledOffset = offset
2322				local pos = getCollisionPoint(fP, fX*scaledOffset.x + fY*scaledOffset.y)
2323				if queryPoint(pos, (fP + fZ*dist - pos).Unit, dist) == inf then
2324					return false
2325				end
2326			end
2327	
2328			debug.profileend()
2329		end
2330	
2331		debug.profileend()
2332		return true
2333	end
2334	
2335	local function Popper(focus, targetDist, focusExtrapolation)
2336		debug.profilebegin("popper")
2337	
2338		subjectRoot = subjectPart and subjectPart:GetRootPart() or subjectPart
2339	
2340		local dist = targetDist
2341		local soft, hard = queryViewport(focus, targetDist)
2342		if hard < dist then
2343			dist = hard
2344		end
2345		if soft < dist and testPromotion(focus, targetDist, focusExtrapolation) then
2346			dist = soft
2347		end
2348	
2349		subjectRoot = nil
2350	
2351		debug.profileend()
2352		return dist
2353	end
2354	
2355	return Popper
2356end
2357
2358function _ZoomController()
2359	local ZOOM_STIFFNESS = 4.5
2360	local ZOOM_DEFAULT = 12.5
2361	local ZOOM_ACCELERATION = 0.0375
2362	
2363	local MIN_FOCUS_DIST = 0.5
2364	local DIST_OPAQUE = 1
2365	
2366	local Popper = _Popper()
2367	
2368	local clamp = math.clamp
2369	local exp = math.exp
2370	local min = math.min
2371	local max = math.max
2372	local pi = math.pi
2373	
2374	local cameraMinZoomDistance, cameraMaxZoomDistance do
2375		local Player = game:GetService("Players").LocalPlayer
2376	
2377		local function updateBounds()
2378			cameraMinZoomDistance = Player.CameraMinZoomDistance
2379			cameraMaxZoomDistance = Player.CameraMaxZoomDistance
2380		end
2381	
2382		updateBounds()
2383	
2384		Player:GetPropertyChangedSignal("CameraMinZoomDistance"):Connect(updateBounds)
2385		Player:GetPropertyChangedSignal("CameraMaxZoomDistance"):Connect(updateBounds)
2386	end
2387	
2388	local ConstrainedSpring = {} do
2389		ConstrainedSpring.__index = ConstrainedSpring
2390	
2391		function ConstrainedSpring.new(freq, x, minValue, maxValue)
2392			x = clamp(x, minValue, maxValue)
2393			return setmetatable({
2394				freq = freq, -- Undamped frequency (Hz)
2395				x = x, -- Current position
2396				v = 0, -- Current velocity
2397				minValue = minValue, -- Minimum bound
2398				maxValue = maxValue, -- Maximum bound
2399				goal = x, -- Goal position
2400			}, ConstrainedSpring)
2401		end
2402	
2403		function ConstrainedSpring:Step(dt)
2404			local freq = self.freq*2*pi -- Convert from Hz to rad/s
2405			local x = self.x
2406			local v = self.v
2407			local minValue = self.minValue
2408			local maxValue = self.maxValue
2409			local goal = self.goal
2410	
2411			-- Solve the spring ODE for position and velocity after time t, assuming critical damping:
2412			--   2*f*x'[t] + x''[t] = f^2*(g - x[t])
2413			-- Knowns are x[0] and x'[0].
2414			-- Solve for x[t] and x'[t].
2415	
2416			local offset = goal - x
2417			local step = freq*dt
2418			local decay = exp(-step)
2419	
2420			local x1 = goal + (v*dt - offset*(step + 1))*decay
2421			local v1 = ((offset*freq - v)*step + v)*decay
2422	
2423			-- Constrain
2424			if x1 < minValue then
2425				x1 = minValue
2426				v1 = 0
2427			elseif x1 > maxValue then
2428				x1 = maxValue
2429				v1 = 0
2430			end
2431	
2432			self.x = x1
2433			self.v = v1
2434	
2435			return x1
2436		end
2437	end
2438	
2439	local zoomSpring = ConstrainedSpring.new(ZOOM_STIFFNESS, ZOOM_DEFAULT, MIN_FOCUS_DIST, cameraMaxZoomDistance)
2440	
2441	local function stepTargetZoom(z, dz, zoomMin, zoomMax)
2442		z = clamp(z + dz*(1 + z*ZOOM_ACCELERATION), zoomMin, zoomMax)
2443		if z < DIST_OPAQUE then
2444			z = dz <= 0 and zoomMin or DIST_OPAQUE
2445		end
2446		return z
2447	end
2448	
2449	local zoomDelta = 0
2450	
2451	local Zoom = {} do
2452		function Zoom.Update(renderDt, focus, extrapolation)
2453			local poppedZoom = math.huge
2454	
2455			if zoomSpring.goal > DIST_OPAQUE then
2456				-- Make a pessimistic estimate of zoom distance for this step without accounting for poppercam
2457				local maxPossibleZoom = max(
2458					zoomSpring.x,
2459					stepTargetZoom(zoomSpring.goal, zoomDelta, cameraMinZoomDistance, cameraMaxZoomDistance)
2460				)
2461	
2462				-- Run the Popper algorithm on the feasible zoom range, [MIN_FOCUS_DIST, maxPossibleZoom]
2463				poppedZoom = Popper(
2464					focus*CFrame.new(0, 0, MIN_FOCUS_DIST),
2465					maxPossibleZoom - MIN_FOCUS_DIST,
2466					extrapolation
2467				) + MIN_FOCUS_DIST
2468			end
2469	
2470			zoomSpring.minValue = MIN_FOCUS_DIST
2471			zoomSpring.maxValue = min(cameraMaxZoomDistance, poppedZoom)
2472	
2473			return zoomSpring:Step(renderDt)
2474		end
2475	
2476		function Zoom.SetZoomParameters(targetZoom, newZoomDelta)
2477			zoomSpring.goal = targetZoom
2478			zoomDelta = newZoomDelta
2479		end
2480	end
2481	
2482	return Zoom
2483end
2484
2485function _MouseLockController()
2486	--[[ Constants ]]--
2487	local DEFAULT_MOUSE_LOCK_CURSOR = "rbxasset://textures/MouseLockedCursor.png"
2488	
2489	local CONTEXT_ACTION_NAME = "MouseLockSwitchAction"
2490	local MOUSELOCK_ACTION_PRIORITY = Enum.ContextActionPriority.Default.Value
2491	
2492	--[[ Services ]]--
2493	local PlayersService = game:GetService("Players")
2494	local ContextActionService = game:GetService("ContextActionService")
2495	local Settings = UserSettings()	-- ignore warning
2496	local GameSettings = Settings.GameSettings
2497	local Mouse = PlayersService.LocalPlayer:GetMouse()
2498	
2499	--[[ The Module ]]--
2500	local MouseLockController = {}
2501	MouseLockController.__index = MouseLockController
2502	
2503	function MouseLockController.new()
2504		local self = setmetatable({}, MouseLockController)
2505	
2506		self.isMouseLocked = false
2507		self.savedMouseCursor = nil
2508		self.boundKeys = {Enum.KeyCode.LeftShift, Enum.KeyCode.RightShift} -- defaults
2509	
2510		self.mouseLockToggledEvent = Instance.new("BindableEvent")
2511	
2512		local boundKeysObj = script:FindFirstChild("BoundKeys")
2513		if (not boundKeysObj) or (not boundKeysObj:IsA("StringValue")) then
2514			-- If object with correct name was found, but it's not a StringValue, destroy and replace
2515			if boundKeysObj then
2516				boundKeysObj:Destroy()
2517			end
2518	
2519			boundKeysObj = Instance.new("StringValue")
2520			boundKeysObj.Name = "BoundKeys"
2521			boundKeysObj.Value = "LeftShift,RightShift"
2522			boundKeysObj.Parent = script
2523		end
2524	
2525		if boundKeysObj then
2526			boundKeysObj.Changed:Connect(function(value)
2527				self:OnBoundKeysObjectChanged(value)
2528			end)
2529			self:OnBoundKeysObjectChanged(boundKeysObj.Value) -- Initial setup call
2530		end
2531	
2532		-- Watch for changes to user's ControlMode and ComputerMovementMode settings and update the feature availability accordingly
2533		GameSettings.Changed:Connect(function(property)
2534			if property == "ControlMode" or property == "ComputerMovementMode" then
2535				self:UpdateMouseLockAvailability()
2536			end
2537		end)
2538	
2539		-- Watch for changes to DevEnableMouseLock and update the feature availability accordingly
2540		PlayersService.LocalPlayer:GetPropertyChangedSignal("DevEnableMouseLock"):Connect(function()
2541			self:UpdateMouseLockAvailability()
2542		end)
2543	
2544		-- Watch for changes to DevEnableMouseLock and update the feature availability accordingly
2545		PlayersService.LocalPlayer:GetPropertyChangedSignal("DevComputerMovementMode"):Connect(function()
2546			self:UpdateMouseLockAvailability()
2547		end)
2548	
2549		self:UpdateMouseLockAvailability()
2550	
2551		return self
2552	end
2553	
2554	function MouseLockController:GetIsMouseLocked()
2555		return self.isMouseLocked
2556	end
2557	
2558	function MouseLockController:GetBindableToggleEvent()
2559		return self.mouseLockToggledEvent.Event
2560	end
2561	
2562	function MouseLockController:GetMouseLockOffset()
2563		local offsetValueObj = script:FindFirstChild("CameraOffset")
2564		if offsetValueObj and offsetValueObj:IsA("Vector3Value") then
2565			return offsetValueObj.Value
2566		else
2567			-- If CameraOffset object was found but not correct type, destroy
2568			if offsetValueObj then
2569				offsetValueObj:Destroy()
2570			end
2571			offsetValueObj = Instance.new("Vector3Value")
2572			offsetValueObj.Name = "CameraOffset"
2573			offsetValueObj.Value = Vector3.new(1.75,0,0) -- Legacy Default Value
2574			offsetValueObj.Parent = script
2575		end
2576	
2577		if offsetValueObj and offsetValueObj.Value then
2578			return offsetValueObj.Value
2579		end
2580	
2581		return Vector3.new(1.75,0,0)
2582	end
2583	
2584	function MouseLockController:UpdateMouseLockAvailability()
2585		local devAllowsMouseLock = PlayersService.LocalPlayer.DevEnableMouseLock
2586		local devMovementModeIsScriptable = PlayersService.LocalPlayer.DevComputerMovementMode == Enum.DevComputerMovementMode.Scriptable
2587		local userHasMouseLockModeEnabled = GameSettings.ControlMode == Enum.ControlMode.MouseLockSwitch
2588		local userHasClickToMoveEnabled =  GameSettings.ComputerMovementMode == Enum.ComputerMovementMode.ClickToMove
2589		local MouseLockAvailable = devAllowsMouseLock and userHasMouseLockModeEnabled and not userHasClickToMoveEnabled and not devMovementModeIsScriptable
2590	
2591		if MouseLockAvailable~=self.enabled then
2592			self:EnableMouseLock(MouseLockAvailable)
2593		end
2594	end
2595	
2596	function MouseLockController:OnBoundKeysObjectChanged(newValue)
2597		self.boundKeys = {} -- Overriding defaults, note: possibly with nothing at all if boundKeysObj.Value is "" or contains invalid values
2598		for token in string.gmatch(newValue,"[^%s,]+") do
2599			for _, keyEnum in pairs(Enum.KeyCode:GetEnumItems()) do
2600				if token == keyEnum.Name then
2601					self.boundKeys[#self.boundKeys+1] = keyEnum
2602					break
2603				end
2604			end
2605		end
2606		self:UnbindContextActions()
2607		self:BindContextActions()
2608	end
2609	
2610	--[[ Local Functions ]]--
2611	function MouseLockController:OnMouseLockToggled()
2612		self.isMouseLocked = not self.isMouseLocked
2613	
2614		if self.isMouseLocked then
2615			local cursorImageValueObj = script:FindFirstChild("CursorImage")
2616			if cursorImageValueObj and cursorImageValueObj:IsA("StringValue") and cursorImageValueObj.Value then
2617				self.savedMouseCursor = Mouse.Icon
2618				Mouse.Icon = cursorImageValueObj.Value
2619			else
2620				if cursorImageValueObj then
2621					cursorImageValueObj:Destroy()
2622				end
2623				cursorImageValueObj = Instance.new("StringValue")
2624				cursorImageValueObj.Name = "CursorImage"
2625				cursorImageValueObj.Value = DEFAULT_MOUSE_LOCK_CURSOR
2626				cursorImageValueObj.Parent = script
2627				self.savedMouseCursor = Mouse.Icon
2628				Mouse.Icon = DEFAULT_MOUSE_LOCK_CURSOR
2629			end
2630		else
2631			if self.savedMouseCursor then
2632				Mouse.Icon = self.savedMouseCursor
2633				self.savedMouseCursor = nil
2634			end
2635		end
2636	
2637		self.mouseLockToggledEvent:Fire()
2638	end
2639	
2640	function MouseLockController:DoMouseLockSwitch(name, state, input)
2641		if state == Enum.UserInputState.Begin then
2642			self:OnMouseLockToggled()
2643			return Enum.ContextActionResult.Sink
2644		end
2645		return Enum.ContextActionResult.Pass
2646	end
2647	
2648	function MouseLockController:BindContextActions()
2649		ContextActionService:BindActionAtPriority(CONTEXT_ACTION_NAME, function(name, state, input)
2650			return self:DoMouseLockSwitch(name, state, input)
2651		end, false, MOUSELOCK_ACTION_PRIORITY, unpack(self.boundKeys))
2652	end
2653	
2654	function MouseLockController:UnbindContextActions()
2655		ContextActionService:UnbindAction(CONTEXT_ACTION_NAME)
2656	end
2657	
2658	function MouseLockController:IsMouseLocked()
2659		return self.enabled and self.isMouseLocked
2660	end
2661	
2662	function MouseLockController:EnableMouseLock(enable)
2663		if enable ~= self.enabled then
2664	
2665			self.enabled = enable
2666	
2667			if self.enabled then
2668				-- Enabling the mode
2669				self:BindContextActions()
2670			else
2671				-- Disabling
2672				-- Restore mouse cursor
2673				if Mouse.Icon~="" then
2674					Mouse.Icon = ""
2675				end
2676	
2677				self:UnbindContextActions()
2678	
2679				-- If the mode is disabled while being used, fire the event to toggle it off
2680				if self.isMouseLocked then
2681					self.mouseLockToggledEvent:Fire()
2682				end
2683	
2684				self.isMouseLocked = false
2685			end
2686	
2687		end
2688	end
2689	
2690	return MouseLockController
2691end
2692
2693function _TransparencyController()
2694	
2695	local MAX_TWEEN_RATE = 2.8 -- per second
2696	
2697	local Util = _CameraUtils()
2698	
2699	--[[ The Module ]]--
2700	local TransparencyController = {}
2701	TransparencyController.__index = TransparencyController
2702	
2703	function TransparencyController.new()
2704		local self = setmetatable({}, TransparencyController)
2705	
2706		self.lastUpdate = tick()
2707		self.transparencyDirty = false
2708		self.enabled = false
2709		self.lastTransparency = nil
2710	
2711		self.descendantAddedConn, self.descendantRemovingConn = nil, nil
2712		self.toolDescendantAddedConns = {}
2713		self.toolDescendantRemovingConns = {}
2714		self.cachedParts = {}
2715	
2716		return self
2717	end
2718	
2719	
2720	function TransparencyController:HasToolAncestor(object)
2721		if object.Parent == nil then return false end
2722		return object.Parent:IsA('Tool') or self:HasToolAncestor(object.Parent)
2723	end
2724	
2725	function TransparencyController:IsValidPartToModify(part)
2726		if part:IsA('BasePart') or part:IsA('Decal') then
2727			return not self:HasToolAncestor(part)
2728		end
2729		return false
2730	end
2731	
2732	function TransparencyController:CachePartsRecursive(object)
2733		if object then
2734			if self:IsValidPartToModify(object) then
2735				self.cachedParts[object] = true
2736				self.transparencyDirty = true
2737			end
2738			for _, child in pairs(object:GetChildren()) do
2739				self:CachePartsRecursive(child)
2740			end
2741		end
2742	end
2743	
2744	function TransparencyController:TeardownTransparency()
2745		for child, _ in pairs(self.cachedParts) do
2746			child.LocalTransparencyModifier = 0
2747		end
2748		self.cachedParts = {}
2749		self.transparencyDirty = true
2750		self.lastTransparency = nil
2751	
2752		if self.descendantAddedConn then
2753			self.descendantAddedConn:disconnect()
2754			self.descendantAddedConn = nil
2755		end
2756		if self.descendantRemovingConn then
2757			self.descendantRemovingConn:disconnect()
2758			self.descendantRemovingConn = nil
2759		end
2760		for object, conn in pairs(self.toolDescendantAddedConns) do
2761			conn:Disconnect()
2762			self.toolDescendantAddedConns[object] = nil
2763		end
2764		for object, conn in pairs(self.toolDescendantRemovingConns) do
2765			conn:Disconnect()
2766			self.toolDescendantRemovingConns[object] = nil
2767		end
2768	end
2769	
2770	function TransparencyController:SetupTransparency(character)
2771		self:TeardownTransparency()
2772	
2773		if self.descendantAddedConn then self.descendantAddedConn:disconnect() end
2774		self.descendantAddedConn = character.DescendantAdded:Connect(function(object)
2775			-- This is a part we want to invisify
2776			if self:IsValidPartToModify(object) then
2777				self.cachedParts[object] = true
2778				self.transparencyDirty = true
2779			-- There is now a tool under the character
2780			elseif object:IsA('Tool') then
2781				if self.toolDescendantAddedConns[object] then self.toolDescendantAddedConns[object]:Disconnect() end
2782				self.toolDescendantAddedConns[object] = object.DescendantAdded:Connect(function(toolChild)
2783					self.cachedParts[toolChild] = nil
2784					if toolChild:IsA('BasePart') or toolChild:IsA('Decal') then
2785						-- Reset the transparency
2786						toolChild.LocalTransparencyModifier = 0
2787					end
2788				end)
2789				if self.toolDescendantRemovingConns[object] then self.toolDescendantRemovingConns[object]:disconnect() end
2790				self.toolDescendantRemovingConns[object] = object.DescendantRemoving:Connect(function(formerToolChild)
2791					wait() -- wait for new parent
2792					if character and formerToolChild and formerToolChild:IsDescendantOf(character) then
2793						if self:IsValidPartToModify(formerToolChild) then
2794							self.cachedParts[formerToolChild] = true
2795							self.transparencyDirty = true
2796						end
2797					end
2798				end)
2799			end
2800		end)
2801		if self.descendantRemovingConn then self.descendantRemovingConn:disconnect() end
2802		self.descendantRemovingConn = character.DescendantRemoving:connect(function(object)
2803			if self.cachedParts[object] then
2804				self.cachedParts[object] = nil
2805				-- Reset the transparency
2806				object.LocalTransparencyModifier = 0
2807			end
2808		end)
2809		self:CachePartsRecursive(character)
2810	end
2811	
2812	
2813	function TransparencyController:Enable(enable)
2814		if self.enabled ~= enable then
2815			self.enabled = enable
2816			self:Update()
2817		end
2818	end
2819	
2820	function TransparencyController:SetSubject(subject)
2821		local character = nil
2822		if subject and subject:IsA("Humanoid") then
2823			character = subject.Parent
2824		end
2825		if subject and subject:IsA("VehicleSeat") and subject.Occupant then
2826			character = subject.Occupant.Parent
2827		end
2828		if character then
2829			self:SetupTransparency(character)
2830		else
2831			self:TeardownTransparency()
2832		end
2833	end
2834	
2835	function TransparencyController:Update()
2836		local instant = false
2837		local now = tick()
2838		local currentCamera = workspace.CurrentCamera
2839	
2840		if currentCamera then
2841			local transparency = 0
2842			if not self.enabled then
2843				instant = true
2844			else
2845				local distance = (currentCamera.Focus.p - currentCamera.CoordinateFrame.p).magnitude
2846				transparency = (distance<2) and (1.0-(distance-0.5)/1.5) or 0 --(7 - distance) / 5
2847				if transparency < 0.5 then
2848					transparency = 0
2849				end
2850	
2851				if self.lastTransparency then
2852					local deltaTransparency = transparency - self.lastTransparency
2853	
2854					-- Don't tween transparency if it is instant or your character was fully invisible last frame
2855					if not instant and transparency < 1 and self.lastTransparency < 0.95 then
2856						local maxDelta = MAX_TWEEN_RATE * (now - self.lastUpdate)
2857						deltaTransparency = math.clamp(deltaTransparency, -maxDelta, maxDelta)
2858					end
2859					transparency = self.lastTransparency + deltaTransparency
2860				else
2861					self.transparencyDirty = true
2862				end
2863	
2864				transparency = math.clamp(Util.Round(transparency, 2), 0, 1)
2865			end
2866	
2867			if self.transparencyDirty or self.lastTransparency ~= transparency then
2868				for child, _ in pairs(self.cachedParts) do
2869					child.LocalTransparencyModifier = transparency
2870				end
2871				self.transparencyDirty = false
2872				self.lastTransparency = transparency
2873			end
2874		end
2875		self.lastUpdate = now
2876	end
2877	
2878	return TransparencyController
2879end
2880
2881function _Poppercam()
2882	local ZoomController =  _ZoomController()
2883	
2884	local TransformExtrapolator = {} do
2885		TransformExtrapolator.__index = TransformExtrapolator
2886	
2887		local CF_IDENTITY = CFrame.new()
2888	
2889		local function cframeToAxis(cframe)
2890			local axis, angle = cframe:toAxisAngle()
2891			return axis*angle
2892		end
2893	
2894		local function axisToCFrame(axis)
2895			local angle = axis.magnitude
2896			if angle > 1e-5 then
2897				return CFrame.fromAxisAngle(axis, angle)
2898			end
2899			return CF_IDENTITY
2900		end
2901	
2902		local function extractRotation(cf)
2903			local _, _, _, xx, yx, zx, xy, yy, zy, xz, yz, zz = cf:components()
2904			return CFrame.new(0, 0, 0, xx, yx, zx, xy, yy, zy, xz, yz, zz)
2905		end
2906	
2907		function TransformExtrapolator.new()
2908			return setmetatable({
2909				lastCFrame = nil,
2910			}, TransformExtrapolator)
2911		end
2912	
2913		function TransformExtrapolator:Step(dt, currentCFrame)
2914			local lastCFrame = self.lastCFrame or currentCFrame
2915			self.lastCFrame = currentCFrame
2916	
2917			local currentPos = currentCFrame.p
2918			local currentRot = extractRotation(currentCFrame)
2919	
2920			local lastPos = lastCFrame.p
2921			local lastRot = extractRotation(lastCFrame)
2922	
2923			-- Estimate velocities from the delta between now and the last frame
2924			-- This estimation can be a little noisy.
2925			local dp = (currentPos - lastPos)/dt
2926			local dr = cframeToAxis(currentRot*lastRot:inverse())/dt
2927	
2928			local function extrapolate(t)
2929				local p = dp*t + currentPos
2930				local r = axisToCFrame(dr*t)*currentRot
2931				return r + p
2932			end
2933	
2934			return {
2935				extrapolate = extrapolate,
2936				posVelocity = dp,
2937				rotVelocity = dr,
2938			}
2939		end
2940	
2941		function TransformExtrapolator:Reset()
2942			self.lastCFrame = nil
2943		end
2944	end
2945	
2946	--[[ The Module ]]--
2947	local BaseOcclusion = _BaseOcclusion()
2948	local Poppercam = setmetatable({}, BaseOcclusion)
2949	Poppercam.__index = Poppercam
2950	
2951	function Poppercam.new()
2952		local self = setmetatable(BaseOcclusion.new(), Poppercam)
2953		self.focusExtrapolator = TransformExtrapolator.new()
2954		return self
2955	end
2956	
2957	function Poppercam:GetOcclusionMode()
2958		return Enum.DevCameraOcclusionMode.Zoom
2959	end
2960	
2961	function Poppercam:Enable(enable)
2962		self.focusExtrapolator:Reset()
2963	end
2964	
2965	function Poppercam:Update(renderDt, desiredCameraCFrame, desiredCameraFocus, cameraController)
2966		local rotatedFocus = CFrame.new(desiredCameraFocus.p, desiredCameraCFrame.p)*CFrame.new(
2967			0, 0, 0,
2968			-1, 0, 0,
2969			0, 1, 0,
2970			0, 0, -1
2971		)
2972		local extrapolation = self.focusExtrapolator:Step(renderDt, rotatedFocus)
2973		local zoom = ZoomController.Update(renderDt, rotatedFocus, extrapolation)
2974		return rotatedFocus*CFrame.new(0, 0, zoom), desiredCameraFocus
2975	end
2976	
2977	-- Called when character is added
2978	function Poppercam:CharacterAdded(character, player)
2979	end
2980	
2981	-- Called when character is about to be removed
2982	function Poppercam:CharacterRemoving(character, player)
2983	end
2984	
2985	function Poppercam:OnCameraSubjectChanged(newSubject)
2986	end
2987	
2988	local ZoomController = _ZoomController()
2989	
2990	function Poppercam:Update(renderDt, desiredCameraCFrame, desiredCameraFocus, cameraController)
2991		local rotatedFocus = desiredCameraFocus * (desiredCameraCFrame - desiredCameraCFrame.p)
2992		local extrapolation = self.focusExtrapolator:Step(renderDt, rotatedFocus)
2993		local zoom = ZoomController.Update(renderDt, rotatedFocus, extrapolation)
2994		return rotatedFocus*CFrame.new(0, 0, zoom), desiredCameraFocus
2995	end
2996	
2997	return Poppercam
2998end
2999
3000function _Invisicam()
3001	
3002	--[[ Top Level Roblox Services ]]--
3003	local PlayersService = game:GetService("Players")
3004	
3005	--[[ Constants ]]--
3006	local ZERO_VECTOR3 = Vector3.new(0,0,0)
3007	local USE_STACKING_TRANSPARENCY = true	-- Multiple items between the subject and camera get transparency values that add up to TARGET_TRANSPARENCY
3008	local TARGET_TRANSPARENCY = 0.75 -- Classic Invisicam's Value, also used by new invisicam for parts hit by head and torso rays
3009	local TARGET_TRANSPARENCY_PERIPHERAL = 0.5 -- Used by new SMART_CIRCLE mode for items not hit by head and torso rays
3010	
3011	local MODE = {
3012		--CUSTOM = 1, 		-- Retired, unused
3013		LIMBS = 2, 			-- Track limbs
3014		MOVEMENT = 3, 		-- Track movement
3015		CORNERS = 4, 		-- Char model corners
3016		CIRCLE1 = 5, 		-- Circle of casts around character
3017		CIRCLE2 = 6, 		-- Circle of casts around character, camera relative
3018		LIMBMOVE = 7, 		-- LIMBS mode + MOVEMENT mode
3019		SMART_CIRCLE = 8, 	-- More sample points on and around character
3020		CHAR_OUTLINE = 9,	-- Dynamic outline around the character
3021	}
3022	
3023	local LIMB_TRACKING_SET = {
3024		-- Body parts common to R15 and R6
3025		['Head'] = true,
3026	
3027		-- Body parts unique to R6
3028		['Left Arm'] = true,
3029		['Right Arm'] = true,
3030		['Left Leg'] = true,
3031		['Right Leg'] = true,
3032	
3033		-- Body parts unique to R15
3034		['LeftLowerArm'] = true,
3035		['RightLowerArm'] = true,
3036		['LeftUpperLeg'] = true,
3037		['RightUpperLeg'] = true
3038	}
3039	
3040	local CORNER_FACTORS = {
3041		Vector3.new(1,1,-1),
3042		Vector3.new(1,-1,-1),
3043		Vector3.new(-1,-1,-1),
3044		Vector3.new(-1,1,-1)
3045	}
3046	
3047	local CIRCLE_CASTS = 10
3048	local MOVE_CASTS = 3
3049	local SMART_CIRCLE_CASTS = 24
3050	local SMART_CIRCLE_INCREMENT = 2.0 * math.pi / SMART_CIRCLE_CASTS
3051	local CHAR_OUTLINE_CASTS = 24
3052	
3053	-- Used to sanitize user-supplied functions
3054	local function AssertTypes(param, ...)
3055		local allowedTypes = {}
3056		local typeString = ''
3057		for _, typeName in pairs({...}) do
3058			allowedTypes[typeName] = true
3059			typeString = typeString .. (typeString == '' and '' or ' or ') .. typeName
3060		end
3061		local theType = type(param)
3062		assert(allowedTypes[theType], typeString .. " type expected, got: " .. theType)
3063	end
3064	
3065	-- Helper function for Determinant of 3x3, not in CameraUtils for performance reasons
3066	local function Det3x3(a,b,c,d,e,f,g,h,i)
3067		return (a*(e*i-f*h)-b*(d*i-f*g)+c*(d*h-e*g))
3068	end
3069	
3070	-- Smart Circle mode needs the intersection of 2 rays that are known to be in the same plane
3071	-- because they are generated from cross products with a common vector. This function is computing
3072	-- that intersection, but it's actually the general solution for the point halfway between where
3073	-- two skew lines come nearest to each other, which is more forgiving.
3074	local function RayIntersection(p0, v0, p1, v1)
3075		local v2 = v0:Cross(v1)
3076		local d1 = p1.x - p0.x
3077		local d2 = p1.y - p0.y
3078		local d3 = p1.z - p0.z
3079		local denom = Det3x3(v0.x,-v1.x,v2.x,v0.y,-v1.y,v2.y,v0.z,-v1.z,v2.z)
3080	
3081		if (denom == 0) then
3082			return ZERO_VECTOR3 -- No solution (rays are parallel)
3083		end
3084	
3085		local t0 = Det3x3(d1,-v1.x,v2.x,d2,-v1.y,v2.y,d3,-v1.z,v2.z) / denom
3086		local t1 = Det3x3(v0.x,d1,v2.x,v0.y,d2,v2.y,v0.z,d3,v2.z) / denom
3087		local s0 = p0 + t0 * v0
3088		local s1 = p1 + t1 * v1
3089		local s = s0 + 0.5 * ( s1 - s0 )
3090	
3091		-- 0.25 studs is a threshold for deciding if the rays are
3092		-- close enough to be considered intersecting, found through testing 
3093		if (s1-s0).Magnitude < 0.25 then
3094			return s
3095		else
3096			return ZERO_VECTOR3
3097		end
3098	end
3099	
3100	
3101	
3102	--[[ The Module ]]--
3103	local BaseOcclusion = _BaseOcclusion()
3104	local Invisicam = setmetatable({}, BaseOcclusion)
3105	Invisicam.__index = Invisicam
3106	
3107	function Invisicam.new()
3108		local self = setmetatable(BaseOcclusion.new(), Invisicam)
3109	
3110		self.char = nil
3111		self.humanoidRootPart = nil
3112		self.torsoPart = nil
3113		self.headPart = nil
3114	
3115		self.childAddedConn = nil
3116		self.childRemovedConn = nil
3117	
3118		self.behaviors = {} 	-- Map of modes to behavior fns
3119		self.behaviors[MODE.LIMBS] = self.LimbBehavior
3120		self.behaviors[MODE.MOVEMENT] = self.MoveBehavior
3121		self.behaviors[MODE.CORNERS] = self.CornerBehavior
3122		self.behaviors[MODE.CIRCLE1] = self.CircleBehavior
3123		self.behaviors[MODE.CIRCLE2] = self.CircleBehavior
3124		self.behaviors[MODE.LIMBMOVE] = self.LimbMoveBehavior
3125		self.behaviors[MODE.SMART_CIRCLE] = self.SmartCircleBehavior
3126		self.behaviors[MODE.CHAR_OUTLINE] = self.CharacterOutlineBehavior
3127	
3128		self.mode = MODE.SMART_CIRCLE
3129		self.behaviorFunction = self.SmartCircleBehavior
3130	
3131		self.savedHits = {} 	-- Objects currently being faded in/out
3132		self.trackedLimbs = {}	-- Used in limb-tracking casting modes
3133	
3134		self.camera = game.Workspace.CurrentCamera
3135	
3136		self.enabled = false
3137		return self
3138	end
3139	
3140	function Invisicam:Enable(enable)
3141		self.enabled = enable
3142	
3143		if not enable then
3144			self:Cleanup()
3145		end
3146	end
3147	
3148	function Invisicam:GetOcclusionMode()
3149		return Enum.DevCameraOcclusionMode.Invisicam
3150	end
3151	
3152	--[[ Module functions ]]--
3153	function Invisicam:LimbBehavior(castPoints)
3154		for limb, _ in pairs(self.trackedLimbs) do
3155			castPoints[#castPoints + 1] = limb.Position
3156		end
3157	end
3158	
3159	function Invisicam:MoveBehavior(castPoints)
3160		for i = 1, MOVE_CASTS do
3161			local position, velocity = self.humanoidRootPart.Position, self.humanoidRootPart.Velocity
3162			local horizontalSpeed = Vector3.new(velocity.X, 0, velocity.Z).Magnitude / 2
3163			local offsetVector = (i - 1) * self.humanoidRootPart.CFrame.lookVector * horizontalSpeed
3164			castPoints[#castPoints + 1] = position + offsetVector
3165		end
3166	end
3167	
3168	function Invisicam:CornerBehavior(castPoints)
3169		local cframe = self.humanoidRootPart.CFrame
3170		local centerPoint = cframe.p
3171		local rotation = cframe - centerPoint
3172		local halfSize = self.char:GetExtentsSize() / 2 --NOTE: Doesn't update w/ limb animations
3173		castPoints[#castPoints + 1] = centerPoint
3174		for i = 1, #CORNER_FACTORS do
3175			castPoints[#castPoints + 1] = centerPoint + (rotation * (halfSize * CORNER_FACTORS[i]))
3176		end
3177	end
3178	
3179	function Invisicam:CircleBehavior(castPoints)
3180		local cframe
3181		if self.mode == MODE.CIRCLE1 then
3182			cframe = self.humanoidRootPart.CFrame
3183		else
3184			local camCFrame = self.camera.CoordinateFrame
3185			cframe = camCFrame - camCFrame.p + self.humanoidRootPart.Position
3186		end
3187		castPoints[#castPoints + 1] = cframe.p
3188		for i = 0, CIRCLE_CASTS - 1 do
3189			local angle = (2 * math.pi / CIRCLE_CASTS) * i
3190			local offset = 3 * Vector3.new(math.cos(angle), math.sin(angle), 0)
3191			castPoints[#castPoints + 1] = cframe * offset
3192		end
3193	end
3194	
3195	function Invisicam:LimbMoveBehavior(castPoints)
3196		self:LimbBehavior(castPoints)
3197		self:MoveBehavior(castPoints)
3198	end
3199	
3200	function Invisicam:CharacterOutlineBehavior(castPoints)
3201		local torsoUp = self.torsoPart.CFrame.upVector.unit
3202		local torsoRight = self.torsoPart.CFrame.rightVector.unit
3203	
3204		-- Torso cross of points for interior coverage
3205		castPoints[#castPoints + 1] = self.torsoPart.CFrame.p
3206		castPoints[#castPoints + 1] = self.torsoPart.CFrame.p + torsoUp
3207		castPoints[#castPoints + 1] = self.torsoPart.CFrame.p - torsoUp
3208		castPoints[#castPoints + 1] = self.torsoPart.CFrame.p + torsoRight
3209		castPoints[#castPoints + 1] = self.torsoPart.CFrame.p - torsoRight
3210		if self.headPart then
3211			castPoints[#castPoints + 1] = self.headPart.CFrame.p
3212		end
3213	
3214		local cframe = CFrame.new(ZERO_VECTOR3,Vector3.new(self.camera.CoordinateFrame.lookVector.X,0,self.camera.CoordinateFrame.lookVector.Z))
3215		local centerPoint = (self.torsoPart and self.torsoPart.Position or self.humanoidRootPart.Position)
3216	
3217		local partsWhitelist = {self.torsoPart}
3218		if self.headPart then
3219			partsWhitelist[#partsWhitelist + 1] = self.headPart
3220		end
3221	
3222		for i = 1, CHAR_OUTLINE_CASTS do
3223			local angle = (2 * math.pi * i / CHAR_OUTLINE_CASTS)
3224			local offset = cframe * (3 * Vector3.new(math.cos(angle), math.sin(angle), 0))
3225	
3226			offset = Vector3.new(offset.X, math.max(offset.Y, -2.25), offset.Z)	
3227	
3228			local ray = Ray.new(centerPoint + offset, -3 * offset)
3229			local hit, hitPoint = game.Workspace:FindPartOnRayWithWhitelist(ray, partsWhitelist, false, false)
3230	
3231			if hit then
3232				-- Use hit point as the cast point, but nudge it slightly inside the character so that bumping up against
3233				-- walls is less likely to cause a transparency glitch
3234				castPoints[#castPoints + 1] = hitPoint + 0.2 * (centerPoint - hitPoint).unit
3235			end
3236		end
3237	end
3238	
3239	function Invisicam:SmartCircleBehavior(castPoints)
3240		local torsoUp = self.torsoPart.CFrame.upVector.unit
3241		local torsoRight = self.torsoPart.CFrame.rightVector.unit
3242	
3243		-- SMART_CIRCLE mode includes rays to head and 5 to the torso.
3244		-- Hands, arms, legs and feet are not included since they
3245		-- are not canCollide and can therefore go inside of parts
3246		castPoints[#castPoints + 1] = self.torsoPart.CFrame.p
3247		castPoints[#castPoints + 1] = self.torsoPart.CFrame.p + torsoUp
3248		castPoints[#castPoints + 1] = self.torsoPart.CFrame.p - torsoUp
3249		castPoints[#castPoints + 1] = self.torsoPart.CFrame.p + torsoRight
3250		castPoints[#castPoints + 1] = self.torsoPart.CFrame.p - torsoRight
3251		if self.headPart then
3252			castPoints[#castPoints + 1] = self.headPart.CFrame.p
3253		end
3254	
3255		local cameraOrientation = self.camera.CFrame - self.camera.CFrame.p
3256		local torsoPoint = Vector3.new(0,0.5,0) + (self.torsoPart and self.torsoPart.Position or self.humanoidRootPart.Position)
3257		local radius = 2.5
3258	
3259		-- This loop first calculates points in a circle of radius 2.5 around the torso of the character, in the
3260		-- plane orthogonal to the camera's lookVector. Each point is then raycast to, to determine if it is within
3261		-- the free space surrounding the player (not inside anything). Two iterations are done to adjust points that
3262		-- are inside parts, to try to move them to valid locations that are still on their camera ray, so that the
3263		-- circle remains circular from the camera's perspective, but does not cast rays into walls or parts that are
3264		-- behind, below or beside the character and not really obstructing view of the character. This minimizes
3265		-- the undesirable situation where the character walks up to an exterior wall and it is made invisible even
3266		-- though it is behind the character.
3267		for i = 1, SMART_CIRCLE_CASTS do
3268			local angle = SMART_CIRCLE_INCREMENT * i - 0.5 * math.pi
3269			local offset = radius * Vector3.new(math.cos(angle), math.sin(angle), 0)
3270			local circlePoint = torsoPoint + cameraOrientation * offset
3271	
3272			-- Vector from camera to point on the circle being tested
3273			local vp = circlePoint - self.camera.CFrame.p
3274	
3275			local ray = Ray.new(torsoPoint, circlePoint - torsoPoint)
3276			local hit, hp, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {self.char}, false, false )
3277			local castPoint = circlePoint
3278	
3279			if hit then
3280				local hprime = hp + 0.1 * hitNormal.unit -- Slightly offset hit point from the hit surface
3281				local v0 = hprime - torsoPoint -- Vector from torso to offset hit point
3282	
3283				local perp = (v0:Cross(vp)).unit
3284	
3285				-- Vector from the offset hit point, along the hit surface
3286				local v1 = (perp:Cross(hitNormal)).unit
3287	
3288				-- Vector from camera to offset hit
3289				local vprime = (hprime - self.camera.CFrame.p).unit
3290	
3291				-- This dot product checks to see if the vector along the hit surface would hit the correct
3292				-- side of the invisicam cone, or if it would cross the camera look vector and hit the wrong side
3293				if ( v0.unit:Dot(-v1) < v0.unit:Dot(vprime)) then
3294					castPoint = RayIntersection(hprime, v1, circlePoint, vp)
3295	
3296					if castPoint.Magnitude > 0 then
3297						local ray = Ray.new(hprime, castPoint - hprime)
3298						local hit, hitPoint, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {self.char}, false, false )
3299	
3300						if hit then
3301							local hprime2 = hitPoint + 0.1 * hitNormal.unit
3302							castPoint = hprime2
3303						end
3304					else
3305						castPoint = hprime
3306					end
3307				else
3308					castPoint = hprime
3309				end
3310	
3311				local ray = Ray.new(torsoPoint, (castPoint - torsoPoint))
3312				local hit, hitPoint, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {self.char}, false, false )
3313	
3314				if hit then
3315					local castPoint2 = hitPoint - 0.1 * (castPoint - torsoPoint).unit
3316					castPoint = castPoint2
3317				end
3318			end
3319	
3320			castPoints[#castPoints + 1] = castPoint
3321		end
3322	end
3323	
3324	function Invisicam:CheckTorsoReference()
3325		if self.char then
3326			self.torsoPart = self.char:FindFirstChild("Torso")
3327			if not self.torsoPart then
3328				self.torsoPart = self.char:FindFirstChild("UpperTorso")
3329				if not self.torsoPart then
3330					self.torsoPart = self.char:FindFirstChild("HumanoidRootPart")
3331				end
3332			end
3333	
3334			self.headPart = self.char:FindFirstChild("Head")
3335		end
3336	end
3337	
3338	function Invisicam:CharacterAdded(char, player)
3339		-- We only want the LocalPlayer's character
3340		if player~=PlayersService.LocalPlayer then return end
3341	
3342		if self.childAddedConn then
3343			self.childAddedConn:Disconnect()
3344			self.childAddedConn = nil
3345		end
3346		if self.childRemovedConn then
3347			self.childRemovedConn:Disconnect()
3348			self.childRemovedConn = nil
3349		end
3350	
3351		self.char = char
3352	
3353		self.trackedLimbs = {}
3354		local function childAdded(child)
3355			if child:IsA("BasePart") then
3356				if LIMB_TRACKING_SET[child.Name] then
3357					self.trackedLimbs[child] = true
3358				end
3359	
3360				if child.Name == "Torso" or child.Name == "UpperTorso" then
3361					self.torsoPart = child
3362				end
3363	
3364				if child.Name == "Head" then
3365					self.headPart = child
3366				end
3367			end
3368		end
3369	
3370		local function childRemoved(child)
3371			self.trackedLimbs[child] = nil
3372	
3373			-- If removed/replaced part is 'Torso' or 'UpperTorso' double check that we still have a TorsoPart to use
3374			self:CheckTorsoReference()
3375		end
3376	
3377		self.childAddedConn = char.ChildAdded:Connect(childAdded)
3378		self.childRemovedConn = char.ChildRemoved:Connect(childRemoved)
3379		for _, child in pairs(self.char:GetChildren()) do
3380			childAdded(child)
3381		end
3382	end
3383	
3384	function Invisicam:SetMode(newMode)
3385		AssertTypes(newMode, 'number')
3386		for _, modeNum in pairs(MODE) do
3387			if modeNum == newMode then
3388				self.mode = newMode
3389				self.behaviorFunction = self.behaviors[self.mode]
3390				return
3391			end
3392		end
3393		error("Invalid mode number")
3394	end
3395	
3396	function Invisicam:GetObscuredParts()
3397		return self.savedHits
3398	end
3399	
3400	-- Want to turn off Invisicam? Be sure to call this after.
3401	function Invisicam:Cleanup()
3402		for hit, originalFade in pairs(self.savedHits) do
3403			hit.LocalTransparencyModifier = originalFade
3404		end
3405	end
3406	
3407	function Invisicam:Update(dt, desiredCameraCFrame, desiredCameraFocus)
3408		-- Bail if there is no Character
3409		if not self.enabled or not self.char then
3410			return desiredCameraCFrame, desiredCameraFocus
3411		end
3412	
3413		self.camera = game.Workspace.CurrentCamera
3414	
3415		-- TODO: Move this to a GetHumanoidRootPart helper, probably combine with CheckTorsoReference
3416		-- Make sure we still have a HumanoidRootPart
3417		if not self.humanoidRootPart then
3418			local humanoid = self.char:FindFirstChildOfClass("Humanoid")
3419			if humanoid and humanoid.RootPart then
3420				self.humanoidRootPart = humanoid.RootPart
3421			else
3422				-- Not set up with Humanoid? Try and see if there's one in the Character at all:
3423				self.humanoidRootPart = self.char:FindFirstChild("HumanoidRootPart")
3424				if not self.humanoidRootPart then
3425					-- Bail out, since we're relying on HumanoidRootPart existing
3426					return desiredCameraCFrame, desiredCameraFocus
3427				end
3428			end
3429	
3430			-- TODO: Replace this with something more sensible
3431			local ancestryChangedConn
3432			ancestryChangedConn = self.humanoidRootPart.AncestryChanged:Connect(function(child, parent)
3433				if child == self.humanoidRootPart and not parent then 
3434					self.humanoidRootPart = nil
3435					if ancestryChangedConn and ancestryChangedConn.Connected then
3436						ancestryChangedConn:Disconnect()
3437						ancestryChangedConn = nil
3438					end
3439				end
3440			end)
3441		end
3442	
3443		if not self.torsoPart then
3444			self:CheckTorsoReference()
3445			if not self.torsoPart then
3446				-- Bail out, since we're relying on Torso existing, should never happen since we fall back to using HumanoidRootPart as torso
3447				return desiredCameraCFrame, desiredCameraFocus
3448			end
3449		end
3450	
3451		-- Make a list of world points to raycast to
3452		local castPoints = {}
3453		self.behaviorFunction(self, castPoints)
3454	
3455		-- Cast to get a list of objects between the camera and the cast points
3456		local currentHits = {}
3457		local ignoreList = {self.char}
3458		local function add(hit)
3459			currentHits[hit] = true
3460			if not self.savedHits[hit] then
3461				self.savedHits[hit] = hit.LocalTransparencyModifier
3462			end
3463		end
3464	
3465		local hitParts
3466		local hitPartCount = 0
3467	
3468		-- Hash table to treat head-ray-hit parts differently than the rest of the hit parts hit by other rays
3469		-- head/torso ray hit parts will be more transparent than peripheral parts when USE_STACKING_TRANSPARENCY is enabled
3470		local headTorsoRayHitParts = {}
3471	
3472		local perPartTransparencyHeadTorsoHits = TARGET_TRANSPARENCY
3473		local perPartTransparencyOtherHits = TARGET_TRANSPARENCY
3474	
3475		if USE_STACKING_TRANSPARENCY then
3476	
3477			-- This first call uses head and torso rays to find out how many parts are stacked up
3478			-- for the purpose of calculating required per-part transparency
3479			local headPoint = self.headPart and self.headPart.CFrame.p or castPoints[1]
3480			local torsoPoint = self.torsoPart and self.torsoPart.CFrame.p or castPoints[2]
3481			hitParts = self.camera:GetPartsObscuringTarget({headPoint, torsoPoint}, ignoreList)
3482	
3483			-- Count how many things the sample rays passed through, including decals. This should only
3484			-- count decals facing the camera, but GetPartsObscuringTarget does not return surface normals,
3485			-- so my compromise for now is to just let any decal increase the part count by 1. Only one
3486			-- decal per part will be considered.
3487			for i = 1, #hitParts do
3488				local hitPart = hitParts[i]
3489				hitPartCount = hitPartCount + 1 -- count the part itself
3490				headTorsoRayHitParts[hitPart] = true
3491				for _, child in pairs(hitPart:GetChildren()) do
3492					if child:IsA('Decal') or child:IsA('Texture') then
3493						hitPartCount = hitPartCount + 1 -- count first decal hit, then break
3494						break
3495					end
3496				end
3497			end
3498	
3499			if (hitPartCount > 0) then
3500				perPartTransparencyHeadTorsoHits = math.pow( ((0.5 * TARGET_TRANSPARENCY) + (0.5 * TARGET_TRANSPARENCY / hitPartCount)), 1 / hitPartCount )
3501				perPartTransparencyOtherHits = math.pow( ((0.5 * TARGET_TRANSPARENCY_PERIPHERAL) + (0.5 * TARGET_TRANSPARENCY_PERIPHERAL / hitPartCount)), 1 / hitPartCount )
3502			end
3503		end
3504	
3505		-- Now get all the parts hit by all the rays
3506		hitParts = self.camera:GetPartsObscuringTarget(castPoints, ignoreList)
3507	
3508		local partTargetTransparency = {}
3509	
3510		-- Include decals and textures
3511		for i = 1, #hitParts do
3512			local hitPart = hitParts[i]
3513	
3514			partTargetTransparency[hitPart] =headTorsoRayHitParts[hitPart] and perPartTransparencyHeadTorsoHits or perPartTransparencyOtherHits
3515	
3516			-- If the part is not already as transparent or more transparent than what invisicam requires, add it to the list of
3517			-- parts to be modified by invisicam
3518			if hitPart.Transparency < partTargetTransparency[hitPart] then
3519				add(hitPart)
3520			end
3521	
3522			-- Check all decals and textures on the part
3523			for _, child in pairs(hitPart:GetChildren()) do
3524				if child:IsA('Decal') or child:IsA('Texture') then
3525					if (child.Transparency < partTargetTransparency[hitPart]) then
3526						partTargetTransparency[child] = partTargetTransparency[hitPart]
3527						add(child)
3528					end
3529				end
3530			end
3531		end
3532	
3533		-- Invisibilize objects that are in the way, restore those that aren't anymore
3534		for hitPart, originalLTM in pairs(self.savedHits) do
3535			if currentHits[hitPart] then
3536				-- LocalTransparencyModifier gets whatever value is required to print the part's total transparency to equal perPartTransparency
3537				hitPart.LocalTransparencyModifier = (hitPart.Transparency < 1) and ((partTargetTransparency[hitPart] - hitPart.Transparency) / (1.0 - hitPart.Transparency)) or 0
3538			else -- Restore original pre-invisicam value of LTM
3539				hitPart.LocalTransparencyModifier = originalLTM
3540				self.savedHits[hitPart] = nil
3541			end
3542		end
3543	
3544		-- Invisicam does not change the camera values
3545		return desiredCameraCFrame, desiredCameraFocus
3546	end
3547	
3548	return Invisicam
3549end
3550
3551function _LegacyCamera()
3552	
3553	local ZERO_VECTOR2 = Vector2.new(0,0)
3554	
3555	local Util = _CameraUtils()
3556	
3557	--[[ Services ]]--
3558	local PlayersService = game:GetService('Players')
3559	
3560	--[[ The Module ]]--
3561	local BaseCamera = _BaseCamera()
3562	local LegacyCamera = setmetatable({}, BaseCamera)
3563	LegacyCamera.__index = LegacyCamera
3564	
3565	function LegacyCamera.new()
3566		local self = setmetatable(BaseCamera.new(), LegacyCamera)
3567	
3568		self.cameraType = Enum.CameraType.Fixed
3569		self.lastUpdate = tick()
3570		self.lastDistanceToSubject = nil
3571	
3572		return self
3573	end
3574	
3575	function LegacyCamera:GetModuleName()
3576		return "LegacyCamera"
3577	end
3578	
3579	--[[ Functions overridden from BaseCamera ]]--
3580	function LegacyCamera:SetCameraToSubjectDistance(desiredSubjectDistance)
3581		return BaseCamera.SetCameraToSubjectDistance(self,desiredSubjectDistance)
3582	end
3583	
3584	function LegacyCamera:Update(dt)
3585	
3586		-- Cannot update until cameraType has been set
3587		if not self.cameraType then return end
3588	
3589		local now = tick()
3590		local timeDelta = (now - self.lastUpdate)
3591		local camera = 	workspace.CurrentCamera
3592		local newCameraCFrame = camera.CFrame
3593		local newCameraFocus = camera.Focus
3594		local player = PlayersService.LocalPlayer
3595	
3596		if self.lastUpdate == nil or timeDelta > 1 then
3597			self.lastDistanceToSubject = nil
3598		end
3599		local subjectPosition = self:GetSubjectPosition()
3600	
3601		if self.cameraType == Enum.CameraType.Fixed then
3602			if self.lastUpdate then
3603				-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
3604				local delta = math.min(0.1, now - self.lastUpdate)
3605				local gamepadRotation = self:UpdateGamepad()
3606				self.rotateInput = self.rotateInput + (gamepadRotation * delta)
3607			end
3608	
3609			if subjectPosition and player and camera then
3610				local distanceToSubject = self:GetCameraToSubjectDistance()
3611				local newLookVector = self:CalculateNewLookVector()
3612				self.rotateInput = ZERO_VECTOR2
3613	
3614				newCameraFocus = camera.Focus -- Fixed camera does not change focus
3615				newCameraCFrame = CFrame.new(camera.CFrame.p, camera.CFrame.p + (distanceToSubject * newLookVector))
3616			end
3617		elseif self.cameraType == Enum.CameraType.Attach then
3618			if subjectPosition and camera then
3619				local distanceToSubject = self:GetCameraToSubjectDistance()
3620				local humanoid = self:GetHumanoid()
3621				if self.lastUpdate and humanoid and humanoid.RootPart then
3622	
3623					-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
3624					local delta = math.min(0.1, now - self.lastUpdate)
3625					local gamepadRotation = self:UpdateGamepad()
3626					self.rotateInput = self.rotateInput + (gamepadRotation * delta)
3627	
3628					local forwardVector = humanoid.RootPart.CFrame.lookVector
3629	
3630					local y = Util.GetAngleBetweenXZVectors(forwardVector, self:GetCameraLookVector())
3631					if Util.IsFinite(y) then
3632						-- Preserve vertical rotation from user input
3633						self.rotateInput = Vector2.new(y, self.rotateInput.Y)
3634					end
3635				end
3636	
3637				local newLookVector = self:CalculateNewLookVector()
3638				self.rotateInput = ZERO_VECTOR2
3639	
3640				newCameraFocus = CFrame.new(subjectPosition)
3641				newCameraCFrame = CFrame.new(subjectPosition - (distanceToSubject * newLookVector), subjectPosition)
3642			end
3643		elseif self.cameraType == Enum.CameraType.Watch then
3644			if subjectPosition and player and camera then
3645				local cameraLook = nil
3646	
3647				local humanoid = self:GetHumanoid()
3648				if humanoid and humanoid.RootPart then
3649					local diffVector = subjectPosition - camera.CFrame.p
3650					cameraLook = diffVector.unit
3651	
3652					if self.lastDistanceToSubject and self.lastDistanceToSubject == self:GetCameraToSubjectDistance() then
3653						-- Don't clobber the zoom if they zoomed the camera
3654						local newDistanceToSubject = diffVector.magnitude
3655						self:SetCameraToSubjectDistance(newDistanceToSubject)
3656					end
3657				end
3658	
3659				local distanceToSubject = self:GetCameraToSubjectDistance()
3660				local newLookVector = self:CalculateNewLookVector(cameraLook)
3661				self.rotateInput = ZERO_VECTOR2
3662	
3663				newCameraFocus = CFrame.new(subjectPosition)
3664				newCameraCFrame = CFrame.new(subjectPosition - (distanceToSubject * newLookVector), subjectPosition)
3665	
3666				self.lastDistanceToSubject = distanceToSubject
3667			end
3668		else
3669			-- Unsupported type, return current values unchanged
3670			return camera.CFrame, camera.Focus
3671		end
3672	
3673		self.lastUpdate = now
3674		return newCameraCFrame, newCameraFocus
3675	end
3676	
3677	return LegacyCamera
3678end
3679
3680function _OrbitalCamera()
3681	
3682	-- Local private variables and constants
3683	local UNIT_Z = Vector3.new(0,0,1)
3684	local X1_Y0_Z1 = Vector3.new(1,0,1)	--Note: not a unit vector, used for projecting onto XZ plane
3685	local ZERO_VECTOR3 = Vector3.new(0,0,0)
3686	local ZERO_VECTOR2 = Vector2.new(0,0)
3687	local TAU = 2 * math.pi
3688	
3689	--[[ Gamepad Support ]]--
3690	local THUMBSTICK_DEADZONE = 0.2
3691	
3692	-- Do not edit these values, they are not the developer-set limits, they are limits
3693	-- to the values the camera system equations can correctly handle
3694	local MIN_ALLOWED_ELEVATION_DEG = -80
3695	local MAX_ALLOWED_ELEVATION_DEG = 80
3696	
3697	local externalProperties = {}
3698	externalProperties["InitialDistance"]  = 25
3699	externalProperties["MinDistance"]      = 10
3700	externalProperties["MaxDistance"]      = 100
3701	externalProperties["InitialElevation"] = 35
3702	externalProperties["MinElevation"]     = 35
3703	externalProperties["MaxElevation"]     = 35
3704	externalProperties["ReferenceAzimuth"] = -45	-- Angle around the Y axis where the camera starts. -45 offsets the camera in the -X and +Z directions equally
3705	externalProperties["CWAzimuthTravel"]  = 90	-- How many degrees the camera is allowed to rotate from the reference position, CW as seen from above
3706	externalProperties["CCWAzimuthTravel"] = 90	-- How many degrees the camera is allowed to rotate from the reference position, CCW as seen from above
3707	externalProperties["UseAzimuthLimits"] = false -- Full rotation around Y axis available by default
3708	
3709	local Util = _CameraUtils()
3710	
3711	--[[ Services ]]--
3712	local PlayersService = game:GetService('Players')
3713	local VRService = game:GetService("VRService")
3714	
3715	--[[ The Module ]]--
3716	local BaseCamera = _BaseCamera()
3717	local OrbitalCamera = setmetatable({}, BaseCamera)
3718	OrbitalCamera.__index = OrbitalCamera
3719	
3720	
3721	function OrbitalCamera.new()
3722		local self = setmetatable(BaseCamera.new(), OrbitalCamera)
3723	
3724		self.lastUpdate = tick()
3725	
3726		-- OrbitalCamera-specific members
3727		self.changedSignalConnections = {}
3728		self.refAzimuthRad = nil
3729		self.curAzimuthRad = nil
3730		self.minAzimuthAbsoluteRad = nil
3731		self.maxAzimuthAbsoluteRad = nil
3732		self.useAzimuthLimits = nil
3733		self.curElevationRad = nil
3734		self.minElevationRad = nil
3735		self.maxElevationRad = nil
3736		self.curDistance = nil
3737		self.minDistance = nil
3738		self.maxDistance = nil
3739	
3740		-- Gamepad
3741		self.r3ButtonDown = false
3742		self.l3ButtonDown = false
3743		self.gamepadDollySpeedMultiplier = 1
3744	
3745		self.lastUserPanCamera = tick()
3746	
3747		self.externalProperties = {}
3748		self.externalProperties["InitialDistance"] 	= 25
3749		self.externalProperties["MinDistance"] 		= 10
3750		self.externalProperties["MaxDistance"] 		= 100
3751		self.externalProperties["InitialElevation"] 	= 35
3752		self.externalProperties["MinElevation"] 		= 35
3753		self.externalProperties["MaxElevation"] 		= 35
3754		self.externalProperties["ReferenceAzimuth"] 	= -45	-- Angle around the Y axis where the camera starts. -45 offsets the camera in the -X and +Z directions equally
3755		self.externalProperties["CWAzimuthTravel"] 	= 90	-- How many degrees the camera is allowed to rotate from the reference position, CW as seen from above
3756		self.externalProperties["CCWAzimuthTravel"] 	= 90	-- How many degrees the camera is allowed to rotate from the reference position, CCW as seen from above
3757		self.externalProperties["UseAzimuthLimits"] 	= false -- Full rotation around Y axis available by default
3758		self:LoadNumberValueParameters()
3759	
3760		return self
3761	end
3762	
3763	function OrbitalCamera:LoadOrCreateNumberValueParameter(name, valueType, updateFunction)
3764		local valueObj = script:FindFirstChild(name)
3765	
3766		if valueObj and valueObj:isA(valueType) then
3767			-- Value object exists and is the correct type, use its value
3768			self.externalProperties[name] = valueObj.Value
3769		elseif self.externalProperties[name] ~= nil then
3770			-- Create missing (or replace incorrectly-typed) valueObject with default value
3771			valueObj = Instance.new(valueType)
3772			valueObj.Name = name
3773			valueObj.Parent = script
3774			valueObj.Value = self.externalProperties[name]
3775		else
3776			print("externalProperties table has no entry for ",name)
3777			return
3778		end
3779	
3780		if updateFunction then
3781			if self.changedSignalConnections[name] then
3782				self.changedSignalConnections[name]:Disconnect()
3783			end
3784			self.changedSignalConnections[name] = valueObj.Changed:Connect(function(newValue)
3785				self.externalProperties[name] = newValue
3786				updateFunction(self)
3787			end)
3788		end
3789	end
3790	
3791	function OrbitalCamera:SetAndBoundsCheckAzimuthValues()
3792		self.minAzimuthAbsoluteRad = math.rad(self.externalProperties["ReferenceAzimuth"]) - math.abs(math.rad(self.externalProperties["CWAzimuthTravel"]))
3793		self.maxAzimuthAbsoluteRad = math.rad(self.externalProperties["ReferenceAzimuth"]) + math.abs(math.rad(self.externalProperties["CCWAzimuthTravel"]))
3794		self.useAzimuthLimits = self.externalProperties["UseAzimuthLimits"]
3795		if self.useAzimuthLimits then
3796			self.curAzimuthRad = math.max(self.curAzimuthRad, self.minAzimuthAbsoluteRad)
3797			self.curAzimuthRad = math.min(self.curAzimuthRad, self.maxAzimuthAbsoluteRad)
3798		end
3799	end
3800	
3801	function OrbitalCamera:SetAndBoundsCheckElevationValues()
3802		-- These degree values are the direct user input values. It is deliberate that they are
3803		-- ranged checked only against the extremes, and not against each other. Any time one
3804		-- is changed, both of the internal values in radians are recalculated. This allows for
3805		-- A developer to change the values in any order and for the end results to be that the
3806		-- internal values adjust to match intent as best as possible.
3807		local minElevationDeg = math.max(self.externalProperties["MinElevation"], MIN_ALLOWED_ELEVATION_DEG)
3808		local maxElevationDeg = math.min(self.externalProperties["MaxElevation"], MAX_ALLOWED_ELEVATION_DEG)
3809	
3810		-- Set internal values in radians
3811		self.minElevationRad = math.rad(math.min(minElevationDeg, maxElevationDeg))
3812		self.maxElevationRad = math.rad(math.max(minElevationDeg, maxElevationDeg))
3813		self.curElevationRad = math.max(self.curElevationRad, self.minElevationRad)
3814		self.curElevationRad = math.min(self.curElevationRad, self.maxElevationRad)
3815	end
3816	
3817	function OrbitalCamera:SetAndBoundsCheckDistanceValues()
3818		self.minDistance = self.externalProperties["MinDistance"]
3819		self.maxDistance = self.externalProperties["MaxDistance"]
3820		self.curDistance = math.max(self.curDistance, self.minDistance)
3821		self.curDistance = math.min(self.curDistance, self.maxDistance)
3822	end
3823	
3824	-- This loads from, or lazily creates, NumberValue objects for exposed parameters
3825	function OrbitalCamera:LoadNumberValueParameters()
3826		-- These initial values do not require change listeners since they are read only once
3827		self:LoadOrCreateNumberValueParameter("InitialElevation", "NumberValue", nil)
3828		self:LoadOrCreateNumberValueParameter("InitialDistance", "NumberValue", nil)
3829	
3830		-- Note: ReferenceAzimuth is also used as an initial value, but needs a change listener because it is used in the calculation of the limits
3831		self:LoadOrCreateNumberValueParameter("ReferenceAzimuth", "NumberValue", self.SetAndBoundsCheckAzimuthValue)
3832		self:LoadOrCreateNumberValueParameter("CWAzimuthTravel", "NumberValue", self.SetAndBoundsCheckAzimuthValues)
3833		self:LoadOrCreateNumberValueParameter("CCWAzimuthTravel", "NumberValue", self.SetAndBoundsCheckAzimuthValues)
3834		self:LoadOrCreateNumberValueParameter("MinElevation", "NumberValue", self.SetAndBoundsCheckElevationValues)
3835		self:LoadOrCreateNumberValueParameter("MaxElevation", "NumberValue", self.SetAndBoundsCheckElevationValues)
3836		self:LoadOrCreateNumberValueParameter("MinDistance", "NumberValue", self.SetAndBoundsCheckDistanceValues)
3837		self:LoadOrCreateNumberValueParameter("MaxDistance", "NumberValue", self.SetAndBoundsCheckDistanceValues)
3838		self:LoadOrCreateNumberValueParameter("UseAzimuthLimits", "BoolValue", self.SetAndBoundsCheckAzimuthValues)
3839	
3840		-- Internal values set (in radians, from degrees), plus sanitization
3841		self.curAzimuthRad = math.rad(self.externalProperties["ReferenceAzimuth"])
3842		self.curElevationRad = math.rad(self.externalProperties["InitialElevation"])
3843		self.curDistance = self.externalProperties["InitialDistance"]
3844	
3845		self:SetAndBoundsCheckAzimuthValues()
3846		self:SetAndBoundsCheckElevationValues()
3847		self:SetAndBoundsCheckDistanceValues()
3848	end
3849	
3850	function OrbitalCamera:GetModuleName()
3851		return "OrbitalCamera"
3852	end
3853	
3854	function OrbitalCamera:SetInitialOrientation(humanoid)
3855		if not humanoid or not humanoid.RootPart then
3856			warn("OrbitalCamera could not set initial orientation due to missing humanoid")
3857			return
3858		end
3859		local newDesiredLook = (humanoid.RootPart.CFrame.lookVector - Vector3.new(0,0.23,0)).unit
3860		local horizontalShift = Util.GetAngleBetweenXZVectors(newDesiredLook, self:GetCameraLookVector())
3861		local vertShift = math.asin(self:GetCameraLookVector().y) - math.asin(newDesiredLook.y)
3862		if not Util.IsFinite(horizontalShift) then
3863			horizontalShift = 0
3864		end
3865		if not Util.IsFinite(vertShift) then
3866			vertShift = 0
3867		end
3868		self.rotateInput = Vector2.new(horizontalShift, vertShift)
3869	end
3870	
3871	--[[ Functions of BaseCamera that are overridden by OrbitalCamera ]]--
3872	function OrbitalCamera:GetCameraToSubjectDistance()
3873		return self.curDistance
3874	end
3875	
3876	function OrbitalCamera:SetCameraToSubjectDistance(desiredSubjectDistance)
3877		print("OrbitalCamera SetCameraToSubjectDistance ",desiredSubjectDistance)
3878		local player = PlayersService.LocalPlayer
3879		if player then
3880			self.currentSubjectDistance = math.clamp(desiredSubjectDistance, self.minDistance, self.maxDistance)
3881	
3882			-- OrbitalCamera is not allowed to go into the first-person range
3883			self.currentSubjectDistance = math.max(self.currentSubjectDistance, self.FIRST_PERSON_DISTANCE_THRESHOLD)
3884		end
3885		self.inFirstPerson = false
3886		self:UpdateMouseBehavior()
3887		return self.currentSubjectDistance
3888	end
3889	
3890	function OrbitalCamera:CalculateNewLookVector(suppliedLookVector, xyRotateVector)
3891		local currLookVector = suppliedLookVector or self:GetCameraLookVector()
3892		local currPitchAngle = math.asin(currLookVector.y)
3893		local yTheta = math.clamp(xyRotateVector.y, currPitchAngle - math.rad(MAX_ALLOWED_ELEVATION_DEG), currPitchAngle - math.rad(MIN_ALLOWED_ELEVATION_DEG))
3894		local constrainedRotateInput = Vector2.new(xyRotateVector.x, yTheta)
3895		local startCFrame = CFrame.new(ZERO_VECTOR3, currLookVector)
3896		local newLookVector = (CFrame.Angles(0, -constrainedRotateInput.x, 0) * startCFrame * CFrame.Angles(-constrainedRotateInput.y,0,0)).lookVector
3897		return newLookVector
3898	end
3899	
3900	function OrbitalCamera:GetGamepadPan(name, state, input)
3901		if input.UserInputType == self.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
3902			if self.r3ButtonDown or self.l3ButtonDown then
3903			-- R3 or L3 Thumbstick is depressed, right stick controls dolly in/out
3904				if (input.Position.Y > THUMBSTICK_DEADZONE) then
3905					self.gamepadDollySpeedMultiplier = 0.96
3906				elseif (input.Position.Y < -THUMBSTICK_DEADZONE) then
3907					self.gamepadDollySpeedMultiplier = 1.04
3908				else
3909					self.gamepadDollySpeedMultiplier = 1.00
3910				end
3911			else
3912				if state == Enum.UserInputState.Cancel then
3913					self.gamepadPanningCamera = ZERO_VECTOR2
3914					return
3915				end
3916	
3917				local inputVector = Vector2.new(input.Position.X, -input.Position.Y)
3918				if inputVector.magnitude > THUMBSTICK_DEADZONE then
3919					self.gamepadPanningCamera = Vector2.new(input.Position.X, -input.Position.Y)
3920				else
3921					self.gamepadPanningCamera = ZERO_VECTOR2
3922				end
3923			end
3924			return Enum.ContextActionResult.Sink
3925		end
3926		return Enum.ContextActionResult.Pass
3927	end
3928	
3929	function OrbitalCamera:DoGamepadZoom(name, state, input)
3930		if input.UserInputType == self.activeGamepad and (input.KeyCode == Enum.KeyCode.ButtonR3 or input.KeyCode == Enum.KeyCode.ButtonL3) then
3931			if (state == Enum.UserInputState.Begin) then
3932				self.r3ButtonDown = input.KeyCode == Enum.KeyCode.ButtonR3
3933				self.l3ButtonDown = input.KeyCode == Enum.KeyCode.ButtonL3
3934			elseif (state == Enum.UserInputState.End) then
3935				if (input.KeyCode == Enum.KeyCode.ButtonR3) then
3936					self.r3ButtonDown = false
3937				elseif (input.KeyCode == Enum.KeyCode.ButtonL3) then
3938					self.l3ButtonDown = false
3939				end
3940				if (not self.r3ButtonDown) and (not self.l3ButtonDown) then
3941					self.gamepadDollySpeedMultiplier = 1.00
3942				end
3943			end
3944			return Enum.ContextActionResult.Sink
3945		end
3946		return Enum.ContextActionResult.Pass
3947	end
3948	
3949	function OrbitalCamera:BindGamepadInputActions()
3950		self:BindAction("OrbitalCamGamepadPan", function(name, state, input) return self:GetGamepadPan(name, state, input) end,
3951			false, Enum.KeyCode.Thumbstick2)
3952		self:BindAction("OrbitalCamGamepadZoom", function(name, state, input) return self:DoGamepadZoom(name, state, input) end,
3953			false, Enum.KeyCode.ButtonR3, Enum.KeyCode.ButtonL3)
3954	end
3955	
3956	
3957	-- [[ Update ]]--
3958	function OrbitalCamera:Update(dt)
3959		local now = tick()
3960		local timeDelta = (now - self.lastUpdate)
3961		local userPanningTheCamera = (self.UserPanningTheCamera == true)
3962		local camera = 	workspace.CurrentCamera
3963		local newCameraCFrame = camera.CFrame
3964		local newCameraFocus = camera.Focus
3965		local player = PlayersService.LocalPlayer
3966		local cameraSubject = camera and camera.CameraSubject
3967		local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
3968		local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')
3969	
3970		if self.lastUpdate == nil or timeDelta > 1 then
3971			self.lastCameraTransform = nil
3972		end
3973	
3974		if self.lastUpdate then
3975			local gamepadRotation = self:UpdateGamepad()
3976	
3977			if self:ShouldUseVRRotation() then
3978				self.RotateInput = self.RotateInput + self:GetVRRotationInput()
3979			else
3980				-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
3981				local delta = math.min(0.1, timeDelta)
3982	
3983				if gamepadRotation ~= ZERO_VECTOR2 then
3984					userPanningTheCamera = true
3985					self.rotateInput = self.rotateInput + (gamepadRotation * delta)
3986				end
3987	
3988				local angle = 0
3989				if not (isInVehicle or isOnASkateboard) then
3990					angle = angle + (self.TurningLeft and -120 or 0)
3991					angle = angle + (self.TurningRight and 120 or 0)
3992				end
3993	
3994				if angle ~= 0 then
3995					self.rotateInput = self.rotateInput +  Vector2.new(math.rad(angle * delta), 0)
3996					userPanningTheCamera = true
3997				end
3998			end
3999		end
4000	
4001		-- Reset tween speed if user is panning
4002		if userPanningTheCamera then
4003			self.lastUserPanCamera = tick()
4004		end
4005	
4006		local subjectPosition = self:GetSubjectPosition()
4007	
4008		if subjectPosition and player and camera then
4009	
4010			-- Process any dollying being done by gamepad
4011			-- TODO: Move this
4012			if self.gamepadDollySpeedMultiplier ~= 1 then
4013				self:SetCameraToSubjectDistance(self.currentSubjectDistance * self.gamepadDollySpeedMultiplier)
4014			end
4015	
4016			local VREnabled = VRService.VREnabled
4017			newCameraFocus = VREnabled and self:GetVRFocus(subjectPosition, timeDelta) or CFrame.new(subjectPosition)
4018	
4019			local cameraFocusP = newCameraFocus.p
4020			if VREnabled and not self:IsInFirstPerson() then
4021				local cameraHeight = self:GetCameraHeight()
4022				local vecToSubject = (subjectPosition - camera.CFrame.p)
4023				local distToSubject = vecToSubject.magnitude
4024	
4025				-- Only move the camera if it exceeded a maximum distance to the subject in VR
4026				if distToSubject > self.currentSubjectDistance or self.rotateInput.x ~= 0 then
4027					local desiredDist = math.min(distToSubject, self.currentSubjectDistance)
4028	
4029					-- Note that CalculateNewLookVector is overridden from BaseCamera
4030					vecToSubject = self:CalculateNewLookVector(vecToSubject.unit * X1_Y0_Z1, Vector2.new(self.rotateInput.x, 0)) * desiredDist
4031	
4032					local newPos = cameraFocusP - vecToSubject
4033					local desiredLookDir = camera.CFrame.lookVector
4034					if self.rotateInput.x ~= 0 then
4035						desiredLookDir = vecToSubject
4036					end
4037					local lookAt = Vector3.new(newPos.x + desiredLookDir.x, newPos.y, newPos.z + desiredLookDir.z)
4038					self.RotateInput = ZERO_VECTOR2
4039	
4040					newCameraCFrame = CFrame.new(newPos, lookAt) + Vector3.new(0, cameraHeight, 0)
4041				end
4042			else
4043				-- self.RotateInput is a Vector2 of mouse movement deltas since last update
4044				self.curAzimuthRad = self.curAzimuthRad - self.rotateInput.x
4045	
4046				if self.useAzimuthLimits then
4047					self.curAzimuthRad = math.clamp(self.curAzimuthRad, self.minAzimuthAbsoluteRad, self.maxAzimuthAbsoluteRad)
4048				else
4049					self.curAzimuthRad = (self.curAzimuthRad ~= 0) and (math.sign(self.curAzimuthRad) * (math.abs(self.curAzimuthRad) % TAU)) or 0
4050				end
4051	
4052				self.curElevationRad = math.clamp(self.curElevationRad + self.rotateInput.y, self.minElevationRad, self.maxElevationRad)
4053	
4054				local cameraPosVector = self.currentSubjectDistance * ( CFrame.fromEulerAnglesYXZ( -self.curElevationRad, self.curAzimuthRad, 0 ) * UNIT_Z )
4055				local camPos = subjectPosition + cameraPosVector
4056	
4057				newCameraCFrame = CFrame.new(camPos, subjectPosition)
4058	
4059				self.rotateInput = ZERO_VECTOR2
4060			end
4061	
4062			self.lastCameraTransform = newCameraCFrame
4063			self.lastCameraFocus = newCameraFocus
4064			if (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
4065				self.lastSubjectCFrame = cameraSubject.CFrame
4066			else
4067				self.lastSubjectCFrame = nil
4068			end
4069		end
4070	
4071		self.lastUpdate = now
4072		return newCameraCFrame, newCameraFocus
4073	end
4074	
4075	return OrbitalCamera
4076end
4077
4078function _ClassicCamera()
4079	
4080	-- Local private variables and constants
4081	local ZERO_VECTOR2 = Vector2.new(0,0)
4082	
4083	local tweenAcceleration = math.rad(220)		--Radians/Second^2
4084	local tweenSpeed = math.rad(0)				--Radians/Second
4085	local tweenMaxSpeed = math.rad(250)			--Radians/Second
4086	local TIME_BEFORE_AUTO_ROTATE = 2.0 		--Seconds, used when auto-aligning camera with vehicles
4087	
4088	local INITIAL_CAMERA_ANGLE = CFrame.fromOrientation(math.rad(-15), 0, 0)
4089	
4090	local FFlagUserCameraToggle do
4091		local success, result = pcall(function()
4092			return UserSettings():IsUserFeatureEnabled("UserCameraToggle")
4093		end)
4094		FFlagUserCameraToggle = success and result
4095	end
4096	
4097	--[[ Services ]]--
4098	local PlayersService = game:GetService('Players')
4099	local VRService = game:GetService("VRService")
4100	
4101	local CameraInput = _CameraInput()
4102	local Util = _CameraUtils()
4103	
4104	--[[ The Module ]]--
4105	local BaseCamera = _BaseCamera()
4106	local ClassicCamera = setmetatable({}, BaseCamera)
4107	ClassicCamera.__index = ClassicCamera
4108	
4109	function ClassicCamera.new()
4110		local self = setmetatable(BaseCamera.new(), ClassicCamera)
4111	
4112		self.isFollowCamera = false
4113		self.isCameraToggle = false
4114		self.lastUpdate = tick()
4115		self.cameraToggleSpring = Util.Spring.new(5, 0)
4116	
4117		return self
4118	end
4119	
4120	function ClassicCamera:GetCameraToggleOffset(dt)
4121		assert(FFlagUserCameraToggle)
4122	
4123		if self.isCameraToggle then
4124			local zoom = self.currentSubjectDistance
4125	
4126			if CameraInput.getTogglePan() then
4127				self.cameraToggleSpring.goal = math.clamp(Util.map(zoom, 0.5, self.FIRST_PERSON_DISTANCE_THRESHOLD, 0, 1), 0, 1)
4128			else
4129				self.cameraToggleSpring.goal = 0
4130			end
4131	
4132			local distanceOffset = math.clamp(Util.map(zoom, 0.5, 64, 0, 1), 0, 1) + 1
4133			return Vector3.new(0, self.cameraToggleSpring:step(dt)*distanceOffset, 0)
4134		end
4135	
4136		return Vector3.new()
4137	end
4138	
4139	-- Movement mode standardized to Enum.ComputerCameraMovementMode values
4140	function ClassicCamera:SetCameraMovementMode(cameraMovementMode)
4141		BaseCamera.SetCameraMovementMode(self, cameraMovementMode)
4142	
4143		self.isFollowCamera = cameraMovementMode == Enum.ComputerCameraMovementMode.Follow
4144		self.isCameraToggle = cameraMovementMode == Enum.ComputerCameraMovementMode.CameraToggle
4145	end
4146	
4147	function ClassicCamera:Update()
4148		local now = tick()
4149		local timeDelta = now - self.lastUpdate
4150	
4151		local camera = workspace.CurrentCamera
4152		local newCameraCFrame = camera.CFrame
4153		local newCameraFocus = camera.Focus
4154	
4155		local overrideCameraLookVector = nil
4156		if self.resetCameraAngle then
4157			local rootPart = self:GetHumanoidRootPart()
4158			if rootPart then
4159				overrideCameraLookVector = (rootPart.CFrame * INITIAL_CAMERA_ANGLE).lookVector
4160			else
4161				overrideCameraLookVector = INITIAL_CAMERA_ANGLE.lookVector
4162			end
4163			self.resetCameraAngle = false
4164		end
4165	
4166		local player = PlayersService.LocalPlayer
4167		local humanoid = self:GetHumanoid()
4168		local cameraSubject = camera.CameraSubject
4169		local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
4170		local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')
4171		local isClimbing = humanoid and humanoid:GetState() == Enum.HumanoidStateType.Climbing
4172	
4173		if self.lastUpdate == nil or timeDelta > 1 then
4174			self.lastCameraTransform = nil
4175		end
4176	
4177		if self.lastUpdate then
4178			local gamepadRotation = self:UpdateGamepad()
4179	
4180			if self:ShouldUseVRRotation() then
4181				self.rotateInput = self.rotateInput + self:GetVRRotationInput()
4182			else
4183				-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
4184				local delta = math.min(0.1, timeDelta)
4185	
4186				if gamepadRotation ~= ZERO_VECTOR2 then
4187					self.rotateInput = self.rotateInput + (gamepadRotation * delta)
4188				end
4189	
4190				local angle = 0
4191				if not (isInVehicle or isOnASkateboard) then
4192					angle = angle + (self.turningLeft and -120 or 0)
4193					angle = angle + (self.turningRight and 120 or 0)
4194				end
4195	
4196				if angle ~= 0 then
4197					self.rotateInput = self.rotateInput +  Vector2.new(math.rad(angle * delta), 0)
4198				end
4199			end
4200		end
4201	
4202		local cameraHeight = self:GetCameraHeight()
4203	
4204		-- Reset tween speed if user is panning
4205		if self.userPanningTheCamera then
4206			tweenSpeed = 0
4207			self.lastUserPanCamera = tick()
4208		end
4209	
4210		local userRecentlyPannedCamera = now - self.lastUserPanCamera < TIME_BEFORE_AUTO_ROTATE
4211		local subjectPosition = self:GetSubjectPosition()
4212	
4213		if subjectPosition and player and camera then
4214			local zoom = self:GetCameraToSubjectDistance()
4215			if zoom < 0.5 then
4216				zoom = 0.5
4217			end
4218	
4219			if self:GetIsMouseLocked() and not self:IsInFirstPerson() then
4220				-- We need to use the right vector of the camera after rotation, not before
4221				local newLookCFrame = self:CalculateNewLookCFrame(overrideCameraLookVector)
4222	
4223				local offset = self:GetMouseLockOffset()
4224				local cameraRelativeOffset = offset.X * newLookCFrame.rightVector + offset.Y * newLookCFrame.upVector + offset.Z * newLookCFrame.lookVector
4225	
4226				--offset can be NAN, NAN, NAN if newLookVector has only y component
4227				if Util.IsFiniteVector3(cameraRelativeOffset) then
4228					subjectPosition = subjectPosition + cameraRelativeOffset
4229				end
4230			else
4231				if not self.userPanningTheCamera and self.lastCameraTransform then
4232	
4233					local isInFirstPerson = self:IsInFirstPerson()
4234	
4235					if (isInVehicle or isOnASkateboard or (self.isFollowCamera and isClimbing)) and self.lastUpdate and humanoid and humanoid.Torso then
4236						if isInFirstPerson then
4237							if self.lastSubjectCFrame and (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
4238								local y = -Util.GetAngleBetweenXZVectors(self.lastSubjectCFrame.lookVector, cameraSubject.CFrame.lookVector)
4239								if Util.IsFinite(y) then
4240									self.rotateInput = self.rotateInput + Vector2.new(y, 0)
4241								end
4242								tweenSpeed = 0
4243							end
4244						elseif not userRecentlyPannedCamera then
4245							local forwardVector = humanoid.Torso.CFrame.lookVector
4246							if isOnASkateboard then
4247								forwardVector = cameraSubject.CFrame.lookVector
4248							end
4249	
4250							tweenSpeed = math.clamp(tweenSpeed + tweenAcceleration * timeDelta, 0, tweenMaxSpeed)
4251	
4252							local percent = math.clamp(tweenSpeed * timeDelta, 0, 1)
4253							if self:IsInFirstPerson() and not (self.isFollowCamera and self.isClimbing) then
4254								percent = 1
4255							end
4256	
4257							local y = Util.GetAngleBetweenXZVectors(forwardVector, self:GetCameraLookVector())
4258							if Util.IsFinite(y) and math.abs(y) > 0.0001 then
4259								self.rotateInput = self.rotateInput + Vector2.new(y * percent, 0)
4260							end
4261						end
4262	
4263					elseif self.isFollowCamera and (not (isInFirstPerson or userRecentlyPannedCamera) and not VRService.VREnabled) then
4264						-- Logic that was unique to the old FollowCamera module
4265						local lastVec = -(self.lastCameraTransform.p - subjectPosition)
4266	
4267						local y = Util.GetAngleBetweenXZVectors(lastVec, self:GetCameraLookVector())
4268	
4269						-- This cutoff is to decide if the humanoid's angle of movement,
4270						-- relative to the camera's look vector, is enough that
4271						-- we want the camera to be following them. The point is to provide
4272						-- a sizable dead zone to allow more precise forward movements.
4273						local thetaCutoff = 0.4
4274	
4275						-- Check for NaNs
4276						if Util.IsFinite(y) and math.abs(y) > 0.0001 and math.abs(y) > thetaCutoff * timeDelta then
4277							self.rotateInput = self.rotateInput + Vector2.new(y, 0)
4278						end
4279					end
4280				end
4281			end
4282	
4283			if not self.isFollowCamera then
4284				local VREnabled = VRService.VREnabled
4285	
4286				if VREnabled then
4287					newCameraFocus = self:GetVRFocus(subjectPosition, timeDelta)
4288				else
4289					newCameraFocus = CFrame.new(subjectPosition)
4290				end
4291	
4292				local cameraFocusP = newCameraFocus.p
4293				if VREnabled and not self:IsInFirstPerson() then
4294					local vecToSubject = (subjectPosition - camera.CFrame.p)
4295					local distToSubject = vecToSubject.magnitude
4296	
4297					-- Only move the camera if it exceeded a maximum distance to the subject in VR
4298					if distToSubject > zoom or self.rotateInput.x ~= 0 then
4299						local desiredDist = math.min(distToSubject, zoom)
4300						vecToSubject = self:CalculateNewLookVectorVR() * desiredDist
4301						local newPos = cameraFocusP - vecToSubject
4302						local desiredLookDir = camera.CFrame.lookVector
4303						if self.rotateInput.x ~= 0 then
4304							desiredLookDir = vecToSubject
4305						end
4306						local lookAt = Vector3.new(newPos.x + desiredLookDir.x, newPos.y, newPos.z + desiredLookDir.z)
4307						self.rotateInput = ZERO_VECTOR2
4308	
4309						newCameraCFrame = CFrame.new(newPos, lookAt) + Vector3.new(0, cameraHeight, 0)
4310					end
4311				else
4312					local newLookVector = self:CalculateNewLookVector(overrideCameraLookVector)
4313					self.rotateInput = ZERO_VECTOR2
4314					newCameraCFrame = CFrame.new(cameraFocusP - (zoom * newLookVector), cameraFocusP)
4315				end
4316			else -- is FollowCamera
4317				local newLookVector = self:CalculateNewLookVector(overrideCameraLookVector)
4318				self.rotateInput = ZERO_VECTOR2
4319	
4320				if VRService.VREnabled then
4321					newCameraFocus = self:GetVRFocus(subjectPosition, timeDelta)
4322				else
4323					newCameraFocus = CFrame.new(subjectPosition)
4324				end
4325				newCameraCFrame = CFrame.new(newCameraFocus.p - (zoom * newLookVector), newCameraFocus.p) + Vector3.new(0, cameraHeight, 0)
4326			end
4327	
4328			if FFlagUserCameraToggle then
4329				local toggleOffset = self:GetCameraToggleOffset(timeDelta)
4330				newCameraFocus = newCameraFocus + toggleOffset
4331				newCameraCFrame = newCameraCFrame + toggleOffset
4332			end
4333	
4334			self.lastCameraTransform = newCameraCFrame
4335			self.lastCameraFocus = newCameraFocus
4336			if (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
4337				self.lastSubjectCFrame = cameraSubject.CFrame
4338			else
4339				self.lastSubjectCFrame = nil
4340			end
4341		end
4342	
4343		self.lastUpdate = now
4344		return newCameraCFrame, newCameraFocus
4345	end
4346	
4347	function ClassicCamera:EnterFirstPerson()
4348		self.inFirstPerson = true
4349		self:UpdateMouseBehavior()
4350	end
4351	
4352	function ClassicCamera:LeaveFirstPerson()
4353		self.inFirstPerson = false
4354		self:UpdateMouseBehavior()
4355	end
4356	
4357	return ClassicCamera
4358end
4359
4360function _CameraUtils()
4361
4362	local CameraUtils = {}
4363	
4364	local FFlagUserCameraToggle do
4365		local success, result = pcall(function()
4366			return UserSettings():IsUserFeatureEnabled("UserCameraToggle")
4367		end)
4368		FFlagUserCameraToggle = success and result
4369	end
4370	
4371	local function round(num)
4372		return math.floor(num + 0.5)
4373	end
4374	
4375	-- Critically damped spring class for fluid motion effects
4376	local Spring = {} do
4377		Spring.__index = Spring
4378	
4379		-- Initialize to a given undamped frequency and default position
4380		function Spring.new(freq, pos)
4381			return setmetatable({
4382				freq = freq,
4383				goal = pos,
4384				pos = pos,
4385				vel = 0,
4386			}, Spring)
4387		end
4388	
4389		-- Advance the spring simulation by `dt` seconds
4390		function Spring:step(dt)
4391			local f = self.freq*2*math.pi
4392			local g = self.goal
4393			local p0 = self.pos
4394			local v0 = self.vel
4395	
4396			local offset = p0 - g
4397			local decay = math.exp(-f*dt)
4398	
4399			local p1 = (offset*(1 + f*dt) + v0*dt)*decay + g
4400			local v1 = (v0*(1 - f*dt) - offset*(f*f*dt))*decay
4401	
4402			self.pos = p1
4403			self.vel = v1
4404	
4405			return p1
4406		end
4407	end
4408	
4409	CameraUtils.Spring = Spring
4410	
4411	-- map a value from one range to another
4412	function CameraUtils.map(x, inMin, inMax, outMin, outMax)
4413		return (x - inMin)*(outMax - outMin)/(inMax - inMin) + outMin
4414	end
4415	
4416	-- From TransparencyController
4417	function CameraUtils.Round(num, places)
4418		local decimalPivot = 10^places
4419		return math.floor(num * decimalPivot + 0.5) / decimalPivot
4420	end
4421	
4422	function CameraUtils.IsFinite(val)
4423		return val == val and val ~= math.huge and val ~= -math.huge
4424	end
4425	
4426	function CameraUtils.IsFiniteVector3(vec3)
4427		return CameraUtils.IsFinite(vec3.X) and CameraUtils.IsFinite(vec3.Y) and CameraUtils.IsFinite(vec3.Z)
4428	end
4429	
4430	-- Legacy implementation renamed
4431	function CameraUtils.GetAngleBetweenXZVectors(v1, v2)
4432		return math.atan2(v2.X*v1.Z-v2.Z*v1.X, v2.X*v1.X+v2.Z*v1.Z)
4433	end
4434	
4435	function  CameraUtils.RotateVectorByAngleAndRound(camLook, rotateAngle, roundAmount)
4436		if camLook.Magnitude > 0 then
4437			camLook = camLook.unit
4438			local currAngle = math.atan2(camLook.z, camLook.x)
4439			local newAngle = round((math.atan2(camLook.z, camLook.x) + rotateAngle) / roundAmount) * roundAmount
4440			return newAngle - currAngle
4441		end
4442		return 0
4443	end
4444	
4445	-- K is a tunable parameter that changes the shape of the S-curve
4446	-- the larger K is the more straight/linear the curve gets
4447	local k = 0.35
4448	local lowerK = 0.8
4449	local function SCurveTranform(t)
4450		t = math.clamp(t, -1, 1)
4451		if t >= 0 then
4452			return (k*t) / (k - t + 1)
4453		end
4454		return -((lowerK*-t) / (lowerK + t + 1))
4455	end
4456	
4457	local DEADZONE = 0.1
4458	local function toSCurveSpace(t)
4459		return (1 + DEADZONE) * (2*math.abs(t) - 1) - DEADZONE
4460	end
4461	
4462	local function fromSCurveSpace(t)
4463		return t/2 + 0.5
4464	end
4465	
4466	function CameraUtils.GamepadLinearToCurve(thumbstickPosition)
4467		local function onAxis(axisValue)
4468			local sign = 1
4469			if axisValue < 0 then
4470				sign = -1
4471			end
4472			local point = fromSCurveSpace(SCurveTranform(toSCurveSpace(math.abs(axisValue))))
4473			point = point * sign
4474			return math.clamp(point, -1, 1)
4475		end
4476		return Vector2.new(onAxis(thumbstickPosition.x), onAxis(thumbstickPosition.y))
4477	end
4478	
4479	-- This function converts 4 different, redundant enumeration types to one standard so the values can be compared
4480	function CameraUtils.ConvertCameraModeEnumToStandard(enumValue)
4481		if enumValue == Enum.TouchCameraMovementMode.Default then
4482			return Enum.ComputerCameraMovementMode.Follow
4483		end
4484	
4485		if enumValue == Enum.ComputerCameraMovementMode.Default then
4486			return Enum.ComputerCameraMovementMode.Classic
4487		end
4488	
4489		if enumValue == Enum.TouchCameraMovementMode.Classic or
4490			enumValue == Enum.DevTouchCameraMovementMode.Classic or
4491			enumValue == Enum.DevComputerCameraMovementMode.Classic or
4492			enumValue == Enum.ComputerCameraMovementMode.Classic then
4493			return Enum.ComputerCameraMovementMode.Classic
4494		end
4495	
4496		if enumValue == Enum.TouchCameraMovementMode.Follow or
4497			enumValue == Enum.DevTouchCameraMovementMode.Follow or
4498			enumValue == Enum.DevComputerCameraMovementMode.Follow or
4499			enumValue == Enum.ComputerCameraMovementMode.Follow then
4500			return Enum.ComputerCameraMovementMode.Follow
4501		end
4502	
4503		if enumValue == Enum.TouchCameraMovementMode.Orbital or
4504			enumValue == Enum.DevTouchCameraMovementMode.Orbital or
4505			enumValue == Enum.DevComputerCameraMovementMode.Orbital or
4506			enumValue == Enum.ComputerCameraMovementMode.Orbital then
4507			return Enum.ComputerCameraMovementMode.Orbital
4508		end
4509	
4510		if FFlagUserCameraToggle then
4511			if enumValue == Enum.ComputerCameraMovementMode.CameraToggle or
4512				enumValue == Enum.DevComputerCameraMovementMode.CameraToggle then
4513				return Enum.ComputerCameraMovementMode.CameraToggle
4514			end
4515		end
4516	
4517		-- Note: Only the Dev versions of the Enums have UserChoice as an option
4518		if enumValue == Enum.DevTouchCameraMovementMode.UserChoice or
4519			enumValue == Enum.DevComputerCameraMovementMode.UserChoice then
4520			return Enum.DevComputerCameraMovementMode.UserChoice
4521		end
4522	
4523		-- For any unmapped options return Classic camera
4524		return Enum.ComputerCameraMovementMode.Classic
4525	end
4526	
4527	return CameraUtils
4528end
4529
4530function _CameraModule()
4531	local CameraModule = {}
4532	CameraModule.__index = CameraModule
4533	
4534	local FFlagUserCameraToggle do
4535		local success, result = pcall(function()
4536			return UserSettings():IsUserFeatureEnabled("UserCameraToggle")
4537		end)
4538		FFlagUserCameraToggle = success and result
4539	end
4540	
4541	local FFlagUserRemoveTheCameraApi do
4542		local success, result = pcall(function()
4543			return UserSettings():IsUserFeatureEnabled("UserRemoveTheCameraApi")
4544		end)
4545		FFlagUserRemoveTheCameraApi = success and result
4546	end
4547	
4548	-- NOTICE: Player property names do not all match their StarterPlayer equivalents,
4549	-- with the differences noted in the comments on the right
4550	local PLAYER_CAMERA_PROPERTIES =
4551	{
4552		"CameraMinZoomDistance",
4553		"CameraMaxZoomDistance",
4554		"CameraMode",
4555		"DevCameraOcclusionMode",
4556		"DevComputerCameraMode",			-- Corresponds to StarterPlayer.DevComputerCameraMovementMode
4557		"DevTouchCameraMode",				-- Corresponds to StarterPlayer.DevTouchCameraMovementMode
4558	
4559		-- Character movement mode
4560		"DevComputerMovementMode",
4561		"DevTouchMovementMode",
4562		"DevEnableMouseLock",				-- Corresponds to StarterPlayer.EnableMouseLockOption
4563	}
4564	
4565	local USER_GAME_SETTINGS_PROPERTIES =
4566	{
4567		"ComputerCameraMovementMode",
4568		"ComputerMovementMode",
4569		"ControlMode",
4570		"GamepadCameraSensitivity",
4571		"MouseSensitivity",
4572		"RotationType",
4573		"TouchCameraMovementMode",
4574		"TouchMovementMode",
4575	}
4576	
4577	--[[ Roblox Services ]]--
4578	local Players = game:GetService("Players")
4579	local RunService = game:GetService("RunService")
4580	local UserInputService = game:GetService("UserInputService")
4581	local UserGameSettings = UserSettings():GetService("UserGameSettings")
4582	
4583	-- Camera math utility library
4584	local CameraUtils = _CameraUtils()
4585	
4586	-- Load Roblox Camera Controller Modules
4587	local ClassicCamera = _ClassicCamera()
4588	local OrbitalCamera = _OrbitalCamera()
4589	local LegacyCamera = _LegacyCamera()
4590	
4591	-- Load Roblox Occlusion Modules
4592	local Invisicam = _Invisicam()
4593	local Poppercam = _Poppercam()
4594	
4595	-- Load the near-field character transparency controller and the mouse lock "shift lock" controller
4596	local TransparencyController = _TransparencyController()
4597	local MouseLockController = _MouseLockController()
4598	
4599	-- Table of camera controllers that have been instantiated. They are instantiated as they are used.
4600	local instantiatedCameraControllers = {}
4601	local instantiatedOcclusionModules = {}
4602	
4603	-- Management of which options appear on the Roblox User Settings screen
4604	do
4605		local PlayerScripts = Players.LocalPlayer:WaitForChild("PlayerScripts")
4606	
4607		PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Default)
4608		PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Follow)
4609		PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Classic)
4610	
4611		PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Default)
4612		PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Follow)
4613		PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Classic)
4614		if FFlagUserCameraToggle then
4615			PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.CameraToggle)
4616		end
4617	end
4618	
4619	CameraModule.FFlagUserCameraToggle = FFlagUserCameraToggle
4620	
4621	
4622	function CameraModule.new()
4623		local self = setmetatable({},CameraModule)
4624	
4625		-- Current active controller instances
4626		self.activeCameraController = nil
4627		self.activeOcclusionModule = nil
4628		self.activeTransparencyController = nil
4629		self.activeMouseLockController = nil
4630	
4631		self.currentComputerCameraMovementMode = nil
4632	
4633		-- Connections to events
4634		self.cameraSubjectChangedConn = nil
4635		self.cameraTypeChangedConn = nil
4636	
4637		-- Adds CharacterAdded and CharacterRemoving event handlers for all current players
4638		for _,player in pairs(Players:GetPlayers()) do
4639			self:OnPlayerAdded(player)
4640		end
4641	
4642		-- Adds CharacterAdded and CharacterRemoving event handlers for all players who join in the future
4643		Players.PlayerAdded:Connect(function(player)
4644			self:OnPlayerAdded(player)
4645		end)
4646	
4647		self.activeTransparencyController = TransparencyController.new()
4648		self.activeTransparencyController:Enable(true)
4649	
4650		if not UserInputService.TouchEnabled then
4651			self.activeMouseLockController = MouseLockController.new()
4652			local toggleEvent = self.activeMouseLockController:GetBindableToggleEvent()
4653			if toggleEvent then
4654				toggleEvent:Connect(function()
4655					self:OnMouseLockToggled()
4656				end)
4657			end
4658		end
4659	
4660		self:ActivateCameraController(self:GetCameraControlChoice())
4661		self:ActivateOcclusionModule(Players.LocalPlayer.DevCameraOcclusionMode)
4662		self:OnCurrentCameraChanged() -- Does initializations and makes first camera controller
4663		RunService:BindToRenderStep("cameraRenderUpdate", Enum.RenderPriority.Camera.Value, function(dt) self:Update(dt) end)
4664	
4665		-- Connect listeners to camera-related properties
4666		for _, propertyName in pairs(PLAYER_CAMERA_PROPERTIES) do
4667			Players.LocalPlayer:GetPropertyChangedSignal(propertyName):Connect(function()
4668				self:OnLocalPlayerCameraPropertyChanged(propertyName)
4669			end)
4670		end
4671	
4672		for _, propertyName in pairs(USER_GAME_SETTINGS_PROPERTIES) do
4673			UserGameSettings:GetPropertyChangedSignal(propertyName):Connect(function()
4674				self:OnUserGameSettingsPropertyChanged(propertyName)
4675			end)
4676		end
4677		game.Workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
4678			self:OnCurrentCameraChanged()
4679		end)
4680	
4681		self.lastInputType = UserInputService:GetLastInputType()
4682		UserInputService.LastInputTypeChanged:Connect(function(newLastInputType)
4683			self.lastInputType = newLastInputType
4684		end)
4685	
4686		return self
4687	end
4688	
4689	function CameraModule:GetCameraMovementModeFromSettings()
4690		local cameraMode = Players.LocalPlayer.CameraMode
4691	
4692		-- Lock First Person trumps all other settings and forces ClassicCamera
4693		if cameraMode == Enum.CameraMode.LockFirstPerson then
4694			return CameraUtils.ConvertCameraModeEnumToStandard(Enum.ComputerCameraMovementMode.Classic)
4695		end
4696	
4697		local devMode, userMode
4698		if UserInputService.TouchEnabled then
4699			devMode = CameraUtils.ConvertCameraModeEnumToStandard(Players.LocalPlayer.DevTouchCameraMode)
4700			userMode = CameraUtils.ConvertCameraModeEnumToStandard(UserGameSettings.TouchCameraMovementMode)
4701		else
4702			devMode = CameraUtils.ConvertCameraModeEnumToStandard(Players.LocalPlayer.DevComputerCameraMode)
4703			userMode = CameraUtils.ConvertCameraModeEnumToStandard(UserGameSettings.ComputerCameraMovementMode)
4704		end
4705	
4706		if devMode == Enum.DevComputerCameraMovementMode.UserChoice then
4707			-- Developer is allowing user choice, so user setting is respected
4708			return userMode
4709		end
4710	
4711		return devMode
4712	end
4713	
4714	function CameraModule:ActivateOcclusionModule( occlusionMode )
4715		local newModuleCreator
4716		if occlusionMode == Enum.DevCameraOcclusionMode.Zoom then
4717			newModuleCreator = Poppercam
4718		elseif occlusionMode == Enum.DevCameraOcclusionMode.Invisicam then
4719			newModuleCreator = Invisicam
4720		else
4721			warn("CameraScript ActivateOcclusionModule called with unsupported mode")
4722			return
4723		end
4724	
4725		-- First check to see if there is actually a change. If the module being requested is already
4726		-- the currently-active solution then just make sure it's enabled and exit early
4727		if self.activeOcclusionModule and self.activeOcclusionModule:GetOcclusionMode() == occlusionMode then
4728			if not self.activeOcclusionModule:GetEnabled() then
4729				self.activeOcclusionModule:Enable(true)
4730			end
4731			return
4732		end
4733	
4734		-- Save a reference to the current active module (may be nil) so that we can disable it if
4735		-- we are successful in activating its replacement
4736		local prevOcclusionModule = self.activeOcclusionModule
4737	
4738		-- If there is no active module, see if the one we need has already been instantiated
4739		self.activeOcclusionModule = instantiatedOcclusionModules[newModuleCreator]
4740	
4741		-- If the module was not already instantiated and selected above, instantiate it
4742		if not self.activeOcclusionModule then
4743			self.activeOcclusionModule = newModuleCreator.new()
4744			if self.activeOcclusionModule then
4745				instantiatedOcclusionModules[newModuleCreator] = self.activeOcclusionModule
4746			end
4747		end
4748	
4749		-- If we were successful in either selecting or instantiating the module,
4750		-- enable it if it's not already the currently-active enabled module
4751		if self.activeOcclusionModule then
4752			local newModuleOcclusionMode = self.activeOcclusionModule:GetOcclusionMode()
4753			-- Sanity check that the module we selected or instantiated actually supports the desired occlusionMode
4754			if newModuleOcclusionMode ~= occlusionMode then
4755				warn("CameraScript ActivateOcclusionModule mismatch: ",self.activeOcclusionModule:GetOcclusionMode(),"~=",occlusionMode)
4756			end
4757	
4758			-- Deactivate current module if there is one
4759			if prevOcclusionModule then
4760				-- Sanity check that current module is not being replaced by itself (that should have been handled above)
4761				if prevOcclusionModule ~= self.activeOcclusionModule then
4762					prevOcclusionModule:Enable(false)
4763				else
4764					warn("CameraScript ActivateOcclusionModule failure to detect already running correct module")
4765				end
4766			end
4767	
4768			-- Occlusion modules need to be initialized with information about characters and cameraSubject
4769			-- Invisicam needs the LocalPlayer's character
4770			-- Poppercam needs all player characters and the camera subject
4771			if occlusionMode == Enum.DevCameraOcclusionMode.Invisicam then
4772				-- Optimization to only send Invisicam what we know it needs
4773				if Players.LocalPlayer.Character then
4774					self.activeOcclusionModule:CharacterAdded(Players.LocalPlayer.Character, Players.LocalPlayer )
4775				end
4776			else
4777				-- When Poppercam is enabled, we send it all existing player characters for its raycast ignore list
4778				for _, player in pairs(Players:GetPlayers()) do
4779					if player and player.Character then
4780						self.activeOcclusionModule:CharacterAdded(player.Character, player)
4781					end
4782				end
4783				self.activeOcclusionModule:OnCameraSubjectChanged(game.Workspace.CurrentCamera.CameraSubject)
4784			end
4785	
4786			-- Activate new choice
4787			self.activeOcclusionModule:Enable(true)
4788		end
4789	end
4790	
4791	-- When supplied, legacyCameraType is used and cameraMovementMode is ignored (should be nil anyways)
4792	-- Next, if userCameraCreator is passed in, that is used as the cameraCreator
4793	function CameraModule:ActivateCameraController(cameraMovementMode, legacyCameraType)
4794		local newCameraCreator = nil
4795	
4796		if legacyCameraType~=nil then
4797			--[[
4798				This function has been passed a CameraType enum value. Some of these map to the use of
4799				the LegacyCamera module, the value "Custom" will be translated to a movementMode enum
4800				value based on Dev and User settings, and "Scriptable" will disable the camera controller.
4801			--]]
4802	
4803			if legacyCameraType == Enum.CameraType.Scriptable then
4804				if self.activeCameraController then
4805					self.activeCameraController:Enable(false)
4806					self.activeCameraController = nil
4807					return
4808				end
4809			elseif legacyCameraType == Enum.CameraType.Custom then
4810				cameraMovementMode = self:GetCameraMovementModeFromSettings()
4811	
4812			elseif legacyCameraType == Enum.CameraType.Track then
4813				-- Note: The TrackCamera module was basically an older, less fully-featured
4814				-- version of ClassicCamera, no longer actively maintained, but it is re-implemented in
4815				-- case a game was dependent on its lack of ClassicCamera's extra functionality.
4816				cameraMovementMode = Enum.ComputerCameraMovementMode.Classic
4817	
4818			elseif legacyCameraType == Enum.CameraType.Follow then
4819				cameraMovementMode = Enum.ComputerCameraMovementMode.Follow
4820	
4821			elseif legacyCameraType == Enum.CameraType.Orbital then
4822				cameraMovementMode = Enum.ComputerCameraMovementMode.Orbital
4823	
4824			elseif legacyCameraType == Enum.CameraType.Attach or
4825				   legacyCameraType == Enum.CameraType.Watch or
4826				   legacyCameraType == Enum.CameraType.Fixed then
4827				newCameraCreator = LegacyCamera
4828			else
4829				warn("CameraScript encountered an unhandled Camera.CameraType value: ",legacyCameraType)
4830			end
4831		end
4832	
4833		if not newCameraCreator then
4834			if cameraMovementMode == Enum.ComputerCameraMovementMode.Classic or
4835				cameraMovementMode == Enum.ComputerCameraMovementMode.Follow or
4836				cameraMovementMode == Enum.ComputerCameraMovementMode.Default or
4837				(FFlagUserCameraToggle and cameraMovementMode == Enum.ComputerCameraMovementMode.CameraToggle) then
4838				newCameraCreator = ClassicCamera
4839			elseif cameraMovementMode == Enum.ComputerCameraMovementMode.Orbital then
4840				newCameraCreator = OrbitalCamera
4841			else
4842				warn("ActivateCameraController did not select a module.")
4843				return
4844			end
4845		end
4846	
4847		-- Create the camera control module we need if it does not already exist in instantiatedCameraControllers
4848		local newCameraController
4849		if not instantiatedCameraControllers[newCameraCreator] then
4850			newCameraController = newCameraCreator.new()
4851			instantiatedCameraControllers[newCameraCreator] = newCameraController
4852		else
4853			newCameraController = instantiatedCameraControllers[newCameraCreator]
4854		end
4855	
4856		-- If there is a controller active and it's not the one we need, disable it,
4857		-- if it is the one we need, make sure it's enabled
4858		if self.activeCameraController then
4859			if self.activeCameraController ~= newCameraController then
4860				self.activeCameraController:Enable(false)
4861				self.activeCameraController = newCameraController
4862				self.activeCameraController:Enable(true)
4863			elseif not self.activeCameraController:GetEnabled() then
4864				self.activeCameraController:Enable(true)
4865			end
4866		elseif newCameraController ~= nil then
4867			self.activeCameraController = newCameraController
4868			self.activeCameraController:Enable(true)
4869		end
4870	
4871		if self.activeCameraController then
4872			if cameraMovementMode~=nil then
4873				self.activeCameraController:SetCameraMovementMode(cameraMovementMode)
4874			elseif legacyCameraType~=nil then
4875				-- Note that this is only called when legacyCameraType is not a type that
4876				-- was convertible to a ComputerCameraMovementMode value, i.e. really only applies to LegacyCamera
4877				self.activeCameraController:SetCameraType(legacyCameraType)
4878			end
4879		end
4880	end
4881	
4882	-- Note: The active transparency controller could be made to listen for this event itself.
4883	function CameraModule:OnCameraSubjectChanged()
4884		if self.activeTransparencyController then
4885			self.activeTransparencyController:SetSubject(game.Workspace.CurrentCamera.CameraSubject)
4886		end
4887	
4888		if self.activeOcclusionModule then
4889			self.activeOcclusionModule:OnCameraSubjectChanged(game.Workspace.CurrentCamera.CameraSubject)
4890		end
4891	end
4892	
4893	function CameraModule:OnCameraTypeChanged(newCameraType)
4894		if newCameraType == Enum.CameraType.Scriptable then
4895			if UserInputService.MouseBehavior == Enum.MouseBehavior.LockCenter then
4896				UserInputService.MouseBehavior = Enum.MouseBehavior.Default
4897			end
4898		end
4899	
4900		-- Forward the change to ActivateCameraController to handle
4901		self:ActivateCameraController(nil, newCameraType)
4902	end
4903	
4904	-- Note: Called whenever workspace.CurrentCamera changes, but also on initialization of this script
4905	function CameraModule:OnCurrentCameraChanged()
4906		local currentCamera = game.Workspace.CurrentCamera
4907		if not currentCamera then return end
4908	
4909		if self.cameraSubjectChangedConn then
4910			self.cameraSubjectChangedConn:Disconnect()
4911		end
4912	
4913		if self.cameraTypeChangedConn then
4914			self.cameraTypeChangedConn:Disconnect()
4915		end
4916	
4917		self.cameraSubjectChangedConn = currentCamera:GetPropertyChangedSignal("CameraSubject"):Connect(function()
4918			self:OnCameraSubjectChanged(currentCamera.CameraSubject)
4919		end)
4920	
4921		self.cameraTypeChangedConn = currentCamera:GetPropertyChangedSignal("CameraType"):Connect(function()
4922			self:OnCameraTypeChanged(currentCamera.CameraType)
4923		end)
4924	
4925		self:OnCameraSubjectChanged(currentCamera.CameraSubject)
4926		self:OnCameraTypeChanged(currentCamera.CameraType)
4927	end
4928	
4929	function CameraModule:OnLocalPlayerCameraPropertyChanged(propertyName)
4930		if propertyName == "CameraMode" then
4931			-- CameraMode is only used to turn on/off forcing the player into first person view. The
4932			-- Note: The case "Classic" is used for all other views and does not correspond only to the ClassicCamera module
4933			if Players.LocalPlayer.CameraMode == Enum.CameraMode.LockFirstPerson then
4934				-- Locked in first person, use ClassicCamera which supports this
4935				if not self.activeCameraController or self.activeCameraController:GetModuleName() ~= "ClassicCamera" then
4936					self:ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(Enum.DevComputerCameraMovementMode.Classic))
4937				end
4938	
4939				if self.activeCameraController then
4940					self.activeCameraController:UpdateForDistancePropertyChange()
4941				end
4942			elseif Players.LocalPlayer.CameraMode == Enum.CameraMode.Classic then
4943				-- Not locked in first person view
4944				local cameraMovementMode =self: GetCameraMovementModeFromSettings()
4945				self:ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(cameraMovementMode))
4946			else
4947				warn("Unhandled value for property player.CameraMode: ",Players.LocalPlayer.CameraMode)
4948			end
4949	
4950		elseif propertyName == "DevComputerCameraMode" or 
4951			   propertyName == "DevTouchCameraMode" then
4952			local cameraMovementMode = self:GetCameraMovementModeFromSettings()
4953			self:ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(cameraMovementMode))
4954	
4955		elseif propertyName == "DevCameraOcclusionMode" then
4956			self:ActivateOcclusionModule(Players.LocalPlayer.DevCameraOcclusionMode)
4957	
4958		elseif propertyName == "CameraMinZoomDistance" or propertyName == "CameraMaxZoomDistance" then
4959			if self.activeCameraController then
4960				self.activeCameraController:UpdateForDistancePropertyChange()
4961			end
4962		elseif propertyName == "DevTouchMovementMode" then
4963		elseif propertyName == "DevComputerMovementMode" then
4964		elseif propertyName == "DevEnableMouseLock" then
4965			-- This is the enabling/disabling of "Shift Lock" mode, not LockFirstPerson (which is a CameraMode)
4966			-- Note: Enabling and disabling of MouseLock mode is normally only a publish-time choice made via
4967			-- the corresponding EnableMouseLockOption checkbox of StarterPlayer, and this script does not have
4968			-- support for changing the availability of MouseLock at runtime (this would require listening to
4969			-- Player.DevEnableMouseLock changes)
4970		end
4971	end
4972	
4973	function CameraModule:OnUserGameSettingsPropertyChanged(propertyName)
4974		if propertyName == 	"ComputerCameraMovementMode" then
4975			local cameraMovementMode = self:GetCameraMovementModeFromSettings()
4976			self:ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(cameraMovementMode))
4977		end
4978	end
4979	
4980	--[[
4981		Main RenderStep Update. The camera controller and occlusion module both have opportunities
4982		to set and modify (respectively) the CFrame and Focus before it is set once on CurrentCamera.
4983		The camera and occlusion modules should only return CFrames, not set the CFrame property of
4984		CurrentCamera directly.
4985	--]]
4986	function CameraModule:Update(dt)
4987		if self.activeCameraController then
4988			if FFlagUserCameraToggle then
4989				self.activeCameraController:UpdateMouseBehavior()
4990			end
4991	
4992			local newCameraCFrame, newCameraFocus = self.activeCameraController:Update(dt)
4993			self.activeCameraController:ApplyVRTransform()
4994			if self.activeOcclusionModule then
4995				newCameraCFrame, newCameraFocus = self.activeOcclusionModule:Update(dt, newCameraCFrame, newCameraFocus)
4996			end
4997	
4998			-- Here is where the new CFrame and Focus are set for this render frame
4999			game.Workspace.CurrentCamera.CFrame = newCameraCFrame
5000			game.Workspace.CurrentCamera.Focus = newCameraFocus
5001	
5002			-- Update to character local transparency as needed based on camera-to-subject distance
5003			if self.activeTransparencyController then
5004				self.activeTransparencyController:Update()
5005			end
5006		end
5007	end
5008	
5009	-- Formerly getCurrentCameraMode, this function resolves developer and user camera control settings to
5010	-- decide which camera control module should be instantiated. The old method of converting redundant enum types
5011	function CameraModule:GetCameraControlChoice()
5012		local player = Players.LocalPlayer
5013	
5014		if player then
5015			if self.lastInputType == Enum.UserInputType.Touch or UserInputService.TouchEnabled then
5016				-- Touch
5017				if player.DevTouchCameraMode == Enum.DevTouchCameraMovementMode.UserChoice then
5018					return CameraUtils.ConvertCameraModeEnumToStandard( UserGameSettings.TouchCameraMovementMode )
5019				else
5020					return CameraUtils.ConvertCameraModeEnumToStandard( player.DevTouchCameraMode )
5021				end
5022			else
5023				-- Computer
5024				if player.DevComputerCameraMode == Enum.DevComputerCameraMovementMode.UserChoice then
5025					local computerMovementMode = CameraUtils.ConvertCameraModeEnumToStandard(UserGameSettings.ComputerCameraMovementMode)
5026					return CameraUtils.ConvertCameraModeEnumToStandard(computerMovementMode)
5027				else
5028					return CameraUtils.ConvertCameraModeEnumToStandard(player.DevComputerCameraMode)
5029				end
5030			end
5031		end
5032	end
5033	
5034	function CameraModule:OnCharacterAdded(char, player)
5035		if self.activeOcclusionModule then
5036			self.activeOcclusionModule:CharacterAdded(char, player)
5037		end
5038	end
5039	
5040	function CameraModule:OnCharacterRemoving(char, player)
5041		if self.activeOcclusionModule then
5042			self.activeOcclusionModule:CharacterRemoving(char, player)
5043		end
5044	end
5045	
5046	function CameraModule:OnPlayerAdded(player)
5047		player.CharacterAdded:Connect(function(char)
5048			self:OnCharacterAdded(char, player)
5049		end)
5050		player.CharacterRemoving:Connect(function(char)
5051			self:OnCharacterRemoving(char, player)
5052		end)
5053	end
5054	
5055	function CameraModule:OnMouseLockToggled()
5056		if self.activeMouseLockController then
5057			local mouseLocked = self.activeMouseLockController:GetIsMouseLocked()
5058			local mouseLockOffset = self.activeMouseLockController:GetMouseLockOffset()
5059			if self.activeCameraController then
5060				self.activeCameraController:SetIsMouseLocked(mouseLocked)
5061				self.activeCameraController:SetMouseLockOffset(mouseLockOffset)
5062			end
5063		end
5064	end
5065	--begin edit
5066	local Camera = CameraModule
5067	local IDENTITYCF = CFrame.new()
5068	local lastUpCFrame = IDENTITYCF
5069	
5070	Camera.UpVector = Vector3.new(0, 1, 0)
5071	Camera.TransitionRate = 0.15
5072	Camera.UpCFrame = IDENTITYCF
5073	
5074	function Camera:GetUpVector(oldUpVector)
5075		return oldUpVector
5076	end
5077	local function getRotationBetween(u, v, axis)
5078		local dot, uxv = u:Dot(v), u:Cross(v)
5079		if (dot < -0.99999) then return CFrame.fromAxisAngle(axis, math.pi) end
5080		return CFrame.new(0, 0, 0, uxv.x, uxv.y, uxv.z, 1 + dot)
5081	end
5082	function Camera:CalculateUpCFrame()
5083		local oldUpVector = self.UpVector
5084		local newUpVector = self:GetUpVector(oldUpVector)
5085		
5086		local backup = game.Workspace.CurrentCamera.CFrame.RightVector
5087		local transitionCF = getRotationBetween(oldUpVector, newUpVector, backup)
5088		local vecSlerpCF = IDENTITYCF:Lerp(transitionCF, self.TransitionRate)
5089		
5090		self.UpVector = vecSlerpCF * oldUpVector
5091		self.UpCFrame = vecSlerpCF * self.UpCFrame
5092		
5093		lastUpCFrame = self.UpCFrame
5094	end
5095	
5096	function Camera:Update(dt)
5097		if self.activeCameraController then
5098			if Camera.FFlagUserCameraToggle then
5099				self.activeCameraController:UpdateMouseBehavior()
5100			end
5101			
5102			local newCameraCFrame, newCameraFocus = self.activeCameraController:Update(dt)
5103			self.activeCameraController:ApplyVRTransform()
5104			
5105			self:CalculateUpCFrame()
5106			self.activeCameraController:UpdateUpCFrame(self.UpCFrame)
5107			
5108			-- undo shift-lock offset
5109	
5110			local lockOffset = Vector3.new(0, 0, 0)
5111			if (self.activeMouseLockController and self.activeMouseLockController:GetIsMouseLocked()) then
5112				lockOffset = self.activeMouseLockController:GetMouseLockOffset()
5113			end
5114			
5115			local offset = newCameraFocus:ToObjectSpace(newCameraCFrame)
5116			local camRotation = self.UpCFrame * offset
5117			newCameraFocus = newCameraFocus - newCameraCFrame:VectorToWorldSpace(lockOffset) + camRotation:VectorToWorldSpace(lockOffset)
5118			newCameraCFrame = newCameraFocus * camRotation
5119			
5120			--local offset = newCameraFocus:Inverse() * newCameraCFrame
5121			--newCameraCFrame = newCameraFocus * self.UpCFrame * offset
5122			
5123			if (self.activeCameraController.lastCameraTransform) then
5124				self.activeCameraController.lastCameraTransform = newCameraCFrame
5125				self.activeCameraController.lastCameraFocus = newCameraFocus
5126			end
5127			
5128			if self.activeOcclusionModule then
5129				newCameraCFrame, newCameraFocus = self.activeOcclusionModule:Update(dt, newCameraCFrame, newCameraFocus)
5130			end
5131	
5132			game.Workspace.CurrentCamera.CFrame = newCameraCFrame
5133			game.Workspace.CurrentCamera.Focus = newCameraFocus
5134	
5135			if self.activeTransparencyController then
5136				self.activeTransparencyController:Update()
5137			end
5138		end
5139	end
5140	
5141	function Camera:IsFirstPerson()
5142		if self.activeCameraController then
5143			return self.activeCameraController:InFirstPerson()
5144		end
5145		return false
5146	end
5147	
5148	function Camera:IsMouseLocked()
5149		if self.activeCameraController then
5150			return self.activeCameraController:GetIsMouseLocked()
5151		end
5152		return false
5153	end
5154	function Camera:IsToggleMode()
5155		if self.activeCameraController then
5156			return self.activeCameraController.isCameraToggle
5157		end
5158		return false
5159	end
5160	function Camera:IsCamRelative()
5161		return self:IsMouseLocked() or self:IsFirstPerson()
5162		--return self:IsToggleMode(), self:IsMouseLocked(), self:IsFirstPerson()
5163	end
5164	--
5165	local Utils = _CameraUtils()
5166	function Utils.GetAngleBetweenXZVectors(v1, v2)
5167		local upCFrame = lastUpCFrame
5168		v1 = upCFrame:VectorToObjectSpace(v1)
5169		v2 = upCFrame:VectorToObjectSpace(v2)
5170		return math.atan2(v2.X*v1.Z-v2.Z*v1.X, v2.X*v1.X+v2.Z*v1.Z)
5171	end
5172	--end edit
5173	local cameraModuleObject = CameraModule.new()
5174	local cameraApi = {}
5175	return cameraModuleObject
5176end
5177
5178function _ClickToMoveDisplay()
5179	local ClickToMoveDisplay = {}
5180	
5181	local FAILURE_ANIMATION_ID = "rbxassetid://2874840706"
5182	
5183	local TrailDotIcon = "rbxasset://textures/ui/traildot.png"
5184	local EndWaypointIcon = "rbxasset://textures/ui/waypoint.png"
5185	
5186	local WaypointsAlwaysOnTop = false
5187	
5188	local WAYPOINT_INCLUDE_FACTOR = 2
5189	local LAST_DOT_DISTANCE = 3
5190	
5191	local WAYPOINT_BILLBOARD_SIZE = UDim2.new(0, 1.68 * 25, 0, 2 * 25)
5192	
5193	local ENDWAYPOINT_SIZE_OFFSET_MIN = Vector2.new(0, 0.5)
5194	local ENDWAYPOINT_SIZE_OFFSET_MAX = Vector2.new(0, 1)
5195	
5196	local FAIL_WAYPOINT_SIZE_OFFSET_CENTER = Vector2.new(0, 0.5)
5197	local FAIL_WAYPOINT_SIZE_OFFSET_LEFT = Vector2.new(0.1, 0.5)
5198	local FAIL_WAYPOINT_SIZE_OFFSET_RIGHT = Vector2.new(-0.1, 0.5)
5199	
5200	local FAILURE_TWEEN_LENGTH = 0.125
5201	local FAILURE_TWEEN_COUNT = 4
5202	
5203	local TWEEN_WAYPOINT_THRESHOLD = 5
5204	
5205	local TRAIL_DOT_PARENT_NAME = "ClickToMoveDisplay"
5206	
5207	local TrailDotSize = Vector2.new(1.5, 1.5)
5208	
5209	local TRAIL_DOT_MIN_SCALE = 1
5210	local TRAIL_DOT_MIN_DISTANCE = 10
5211	local TRAIL_DOT_MAX_SCALE = 2.5
5212	local TRAIL_DOT_MAX_DISTANCE = 100
5213	
5214	local PlayersService = game:GetService("Players")
5215	local TweenService = game:GetService("TweenService")
5216	local RunService = game:GetService("RunService")
5217	local Workspace = game:GetService("Workspace")
5218	
5219	local LocalPlayer = PlayersService.LocalPlayer
5220	
5221	local function CreateWaypointTemplates()
5222		local TrailDotTemplate = Instance.new("Part")
5223		TrailDotTemplate.Size = Vector3.new(1, 1, 1)
5224		TrailDotTemplate.Anchored = true
5225		TrailDotTemplate.CanCollide = false
5226		TrailDotTemplate.Name = "TrailDot"
5227		TrailDotTemplate.Transparency = 1
5228		local TrailDotImage = Instance.new("ImageHandleAdornment")
5229		TrailDotImage.Name = "TrailDotImage"
5230		TrailDotImage.Size = TrailDotSize
5231		TrailDotImage.SizeRelativeOffset = Vector3.new(0, 0, -0.1)
5232		TrailDotImage.AlwaysOnTop = WaypointsAlwaysOnTop
5233		TrailDotImage.Image = TrailDotIcon
5234		TrailDotImage.Adornee = TrailDotTemplate
5235		TrailDotImage.Parent = TrailDotTemplate
5236	
5237		local EndWaypointTemplate = Instance.new("Part")
5238		EndWaypointTemplate.Size = Vector3.new(2, 2, 2)
5239		EndWaypointTemplate.Anchored = true
5240		EndWaypointTemplate.CanCollide = false
5241		EndWaypointTemplate.Name = "EndWaypoint"
5242		EndWaypointTemplate.Transparency = 1
5243		local EndWaypointImage = Instance.new("ImageHandleAdornment")
5244		EndWaypointImage.Name = "TrailDotImage"
5245		EndWaypointImage.Size = TrailDotSize
5246		EndWaypointImage.SizeRelativeOffset = Vector3.new(0, 0, -0.1)
5247		EndWaypointImage.AlwaysOnTop = WaypointsAlwaysOnTop
5248		EndWaypointImage.Image = TrailDotIcon
5249		EndWaypointImage.Adornee = EndWaypointTemplate
5250		EndWaypointImage.Parent = EndWaypointTemplate
5251		local EndWaypointBillboard = Instance.new("BillboardGui")
5252		EndWaypointBillboard.Name = "EndWaypointBillboard"
5253		EndWaypointBillboard.Size = WAYPOINT_BILLBOARD_SIZE
5254		EndWaypointBillboard.LightInfluence = 0
5255		EndWaypointBillboard.SizeOffset = ENDWAYPOINT_SIZE_OFFSET_MIN
5256		EndWaypointBillboard.AlwaysOnTop = true
5257		EndWaypointBillboard.Adornee = EndWaypointTemplate
5258		EndWaypointBillboard.Parent = EndWaypointTemplate
5259		local EndWaypointImageLabel = Instance.new("ImageLabel")
5260		EndWaypointImageLabel.Image = EndWaypointIcon
5261		EndWaypointImageLabel.BackgroundTransparency = 1
5262		EndWaypointImageLabel.Size = UDim2.new(1, 0, 1, 0)
5263		EndWaypointImageLabel.Parent = EndWaypointBillboard
5264	
5265	
5266		local FailureWaypointTemplate = Instance.new("Part")
5267		FailureWaypointTemplate.Size = Vector3.new(2, 2, 2)
5268		FailureWaypointTemplate.Anchored = true
5269		FailureWaypointTemplate.CanCollide = false
5270		FailureWaypointTemplate.Name = "FailureWaypoint"
5271		FailureWaypointTemplate.Transparency = 1
5272		local FailureWaypointImage = Instance.new("ImageHandleAdornment")
5273		FailureWaypointImage.Name = "TrailDotImage"
5274		FailureWaypointImage.Size = TrailDotSize
5275		FailureWaypointImage.SizeRelativeOffset = Vector3.new(0, 0, -0.1)
5276		FailureWaypointImage.AlwaysOnTop = WaypointsAlwaysOnTop
5277		FailureWaypointImage.Image = TrailDotIcon
5278		FailureWaypointImage.Adornee = FailureWaypointTemplate
5279		FailureWaypointImage.Parent = FailureWaypointTemplate
5280		local FailureWaypointBillboard = Instance.new("BillboardGui")
5281		FailureWaypointBillboard.Name = "FailureWaypointBillboard"
5282		FailureWaypointBillboard.Size = WAYPOINT_BILLBOARD_SIZE
5283		FailureWaypointBillboard.LightInfluence = 0
5284		FailureWaypointBillboard.SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_CENTER
5285		FailureWaypointBillboard.AlwaysOnTop = true
5286		FailureWaypointBillboard.Adornee = FailureWaypointTemplate
5287		FailureWaypointBillboard.Parent = FailureWaypointTemplate
5288		local FailureWaypointFrame = Instance.new("Frame")
5289		FailureWaypointFrame.BackgroundTransparency = 1
5290		FailureWaypointFrame.Size = UDim2.new(0, 0, 0, 0)
5291		FailureWaypointFrame.Position = UDim2.new(0.5, 0, 1, 0)
5292		FailureWaypointFrame.Parent = FailureWaypointBillboard
5293		local FailureWaypointImageLabel = Instance.new("ImageLabel")
5294		FailureWaypointImageLabel.Image = EndWaypointIcon
5295		FailureWaypointImageLabel.BackgroundTransparency = 1
5296		FailureWaypointImageLabel.Position = UDim2.new(
5297			0, -WAYPOINT_BILLBOARD_SIZE.X.Offset/2, 0, -WAYPOINT_BILLBOARD_SIZE.Y.Offset
5298		)
5299		FailureWaypointImageLabel.Size = WAYPOINT_BILLBOARD_SIZE
5300		FailureWaypointImageLabel.Parent = FailureWaypointFrame
5301	
5302		return TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate
5303	end
5304	
5305	local TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
5306	
5307	local function getTrailDotParent()
5308		local camera = Workspace.CurrentCamera
5309		local trailParent = camera:FindFirstChild(TRAIL_DOT_PARENT_NAME)
5310		if not trailParent then
5311			trailParent = Instance.new("Model")
5312			trailParent.Name = TRAIL_DOT_PARENT_NAME
5313			trailParent.Parent = camera
5314		end
5315		return trailParent
5316	end
5317	
5318	local function placePathWaypoint(waypointModel, position)
5319		local ray = Ray.new(position + Vector3.new(0, 2.5, 0), Vector3.new(0, -10, 0))
5320		local hitPart, hitPoint, hitNormal = Workspace:FindPartOnRayWithIgnoreList(
5321			ray,
5322			{ Workspace.CurrentCamera, LocalPlayer.Character }
5323		)
5324		if hitPart then
5325			waypointModel.CFrame = CFrame.new(hitPoint, hitPoint + hitNormal)
5326			waypointModel.Parent = getTrailDotParent()
5327		end
5328	end
5329	
5330	local TrailDot = {}
5331	TrailDot.__index = TrailDot
5332	
5333	function TrailDot:Destroy()
5334		self.DisplayModel:Destroy()
5335	end
5336	
5337	function TrailDot:NewDisplayModel(position)
5338		local newDisplayModel = TrailDotTemplate:Clone()
5339		placePathWaypoint(newDisplayModel, position)
5340		return newDisplayModel
5341	end
5342	
5343	function TrailDot.new(position, closestWaypoint)
5344		local self = setmetatable({}, TrailDot)
5345	
5346		self.DisplayModel = self:NewDisplayModel(position)
5347		self.ClosestWayPoint = closestWaypoint
5348	
5349		return self
5350	end
5351	
5352	local EndWaypoint = {}
5353	EndWaypoint.__index = EndWaypoint
5354	
5355	function EndWaypoint:Destroy()
5356		self.Destroyed = true
5357		self.Tween:Cancel()
5358		self.DisplayModel:Destroy()
5359	end
5360	
5361	function EndWaypoint:NewDisplayModel(position)
5362		local newDisplayModel = EndWaypointTemplate:Clone()
5363		placePathWaypoint(newDisplayModel, position)
5364		return newDisplayModel
5365	end
5366	
5367	function EndWaypoint:CreateTween()
5368		local tweenInfo = TweenInfo.new(0.5, Enum.EasingStyle.Sine, Enum.EasingDirection.Out, -1, true)
5369		local tween = TweenService:Create(
5370			self.DisplayModel.EndWaypointBillboard,
5371			tweenInfo,
5372			{ SizeOffset = ENDWAYPOINT_SIZE_OFFSET_MAX }
5373		)
5374		tween:Play()
5375		return tween
5376	end
5377	
5378	function EndWaypoint:TweenInFrom(originalPosition)
5379		local currentPositon = self.DisplayModel.Position
5380		local studsOffset = originalPosition - currentPositon
5381		self.DisplayModel.EndWaypointBillboard.StudsOffset = Vector3.new(0, studsOffset.Y, 0)
5382		local tweenInfo = TweenInfo.new(1, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
5383		local tween = TweenService:Create(
5384			self.DisplayModel.EndWaypointBillboard,
5385			tweenInfo,
5386			{ StudsOffset = Vector3.new(0, 0, 0) }
5387		)
5388		tween:Play()
5389		return tween
5390	end
5391	
5392	function EndWaypoint.new(position, closestWaypoint, originalPosition)
5393		local self = setmetatable({}, EndWaypoint)
5394	
5395		self.DisplayModel = self:NewDisplayModel(position)
5396		self.Destroyed = false
5397		if originalPosition and (originalPosition - position).magnitude > TWEEN_WAYPOINT_THRESHOLD then
5398			self.Tween = self:TweenInFrom(originalPosition)
5399			coroutine.wrap(function()
5400				self.Tween.Completed:Wait()
5401				if not self.Destroyed then
5402					self.Tween = self:CreateTween()
5403				end
5404			end)()
5405		else
5406			self.Tween = self:CreateTween()
5407		end
5408		self.ClosestWayPoint = closestWaypoint
5409	
5410		return self
5411	end
5412	
5413	local FailureWaypoint = {}
5414	FailureWaypoint.__index = FailureWaypoint
5415	
5416	function FailureWaypoint:Hide()
5417		self.DisplayModel.Parent = nil
5418	end
5419	
5420	function FailureWaypoint:Destroy()
5421		self.DisplayModel:Destroy()
5422	end
5423	
5424	function FailureWaypoint:NewDisplayModel(position)
5425		local newDisplayModel = FailureWaypointTemplate:Clone()
5426		placePathWaypoint(newDisplayModel, position)
5427		local ray = Ray.new(position + Vector3.new(0, 2.5, 0), Vector3.new(0, -10, 0))
5428		local hitPart, hitPoint, hitNormal = Workspace:FindPartOnRayWithIgnoreList(
5429			ray, { Workspace.CurrentCamera, LocalPlayer.Character }
5430		)
5431		if hitPart then
5432			newDisplayModel.CFrame = CFrame.new(hitPoint, hitPoint + hitNormal)
5433			newDisplayModel.Parent = getTrailDotParent()
5434		end
5435		return newDisplayModel
5436	end
5437	
5438	function FailureWaypoint:RunFailureTween()
5439		wait(FAILURE_TWEEN_LENGTH) -- Delay one tween length betfore starting tweening
5440		-- Tween out from center
5441		local tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH/2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
5442		local tweenLeft = TweenService:Create(self.DisplayModel.FailureWaypointBillboard, tweenInfo,
5443			{ SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_LEFT })
5444		tweenLeft:Play()
5445	
5446		local tweenLeftRoation = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
5447			{ Rotation = 10 })
5448		tweenLeftRoation:Play()
5449	
5450		tweenLeft.Completed:wait()
5451	
5452		-- Tween back and forth
5453		tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH, Enum.EasingStyle.Sine, Enum.EasingDirection.Out,
5454			FAILURE_TWEEN_COUNT - 1, true)
5455		local tweenSideToSide = TweenService:Create(self.DisplayModel.FailureWaypointBillboard, tweenInfo,
5456			{ SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_RIGHT})
5457		tweenSideToSide:Play()
5458	
5459		-- Tween flash dark and roate left and right
5460		tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH, Enum.EasingStyle.Sine, Enum.EasingDirection.Out,
5461			FAILURE_TWEEN_COUNT - 1, true)
5462		local tweenFlash = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame.ImageLabel, tweenInfo,
5463			{ ImageColor3 = Color3.new(0.75, 0.75, 0.75)})
5464		tweenFlash:Play()
5465	
5466		local tweenRotate = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
5467			{ Rotation = -10 })
5468		tweenRotate:Play()
5469	
5470		tweenSideToSide.Completed:wait()
5471	
5472		-- Tween back to center
5473		tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH/2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
5474		local tweenCenter = TweenService:Create(self.DisplayModel.FailureWaypointBillboard, tweenInfo,
5475			{ SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_CENTER })
5476		tweenCenter:Play()
5477	
5478		local tweenRoation = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
5479			{ Rotation = 0 })
5480		tweenRoation:Play()
5481	
5482		tweenCenter.Completed:wait()
5483	
5484		wait(FAILURE_TWEEN_LENGTH) -- Delay one tween length betfore removing
5485	end
5486	
5487	function FailureWaypoint.new(position)
5488		local self = setmetatable({}, FailureWaypoint)
5489	
5490		self.DisplayModel = self:NewDisplayModel(position)
5491	
5492		return self
5493	end
5494	
5495	local failureAnimation = Instance.new("Animation")
5496	failureAnimation.AnimationId = FAILURE_ANIMATION_ID
5497	
5498	local lastHumanoid = nil
5499	local lastFailureAnimationTrack = nil
5500	
5501	local function getFailureAnimationTrack(myHumanoid)
5502		if myHumanoid == lastHumanoid then
5503			return lastFailureAnimationTrack
5504		end
5505		lastFailureAnimationTrack = myHumanoid:LoadAnimation(failureAnimation)
5506		lastFailureAnimationTrack.Priority = Enum.AnimationPriority.Action
5507		lastFailureAnimationTrack.Looped = false
5508		return lastFailureAnimationTrack
5509	end
5510	
5511	local function findPlayerHumanoid()
5512		local character = LocalPlayer.Character
5513		if character then
5514			return character:FindFirstChildOfClass("Humanoid")
5515		end
5516	end
5517	
5518	local function createTrailDots(wayPoints, originalEndWaypoint)
5519		local newTrailDots = {}
5520		local count = 1
5521		for i = 1, #wayPoints - 1 do
5522			local closeToEnd = (wayPoints[i].Position - wayPoints[#wayPoints].Position).magnitude < LAST_DOT_DISTANCE
5523			local includeWaypoint = i % WAYPOINT_INCLUDE_FACTOR == 0 and not closeToEnd
5524			if includeWaypoint then
5525				local trailDot = TrailDot.new(wayPoints[i].Position, i)
5526				newTrailDots[count] = trailDot
5527				count = count + 1
5528			end
5529		end
5530	
5531		local newEndWaypoint = EndWaypoint.new(wayPoints[#wayPoints].Position, #wayPoints, originalEndWaypoint)
5532		table.insert(newTrailDots, newEndWaypoint)
5533	
5534		local reversedTrailDots = {}
5535		count = 1
5536		for i = #newTrailDots, 1, -1 do
5537			reversedTrailDots[count] = newTrailDots[i]
5538			count = count + 1
5539		end
5540		return reversedTrailDots
5541	end
5542	
5543	local function getTrailDotScale(distanceToCamera, defaultSize)
5544		local rangeLength = TRAIL_DOT_MAX_DISTANCE - TRAIL_DOT_MIN_DISTANCE
5545		local inRangePoint = math.clamp(distanceToCamera - TRAIL_DOT_MIN_DISTANCE, 0, rangeLength)/rangeLength
5546		local scale = TRAIL_DOT_MIN_SCALE + (TRAIL_DOT_MAX_SCALE - TRAIL_DOT_MIN_SCALE)*inRangePoint
5547		return defaultSize * scale
5548	end
5549	
5550	local createPathCount = 0
5551	-- originalEndWaypoint is optional, causes the waypoint to tween from that position.
5552	function ClickToMoveDisplay.CreatePathDisplay(wayPoints, originalEndWaypoint)
5553		createPathCount = createPathCount + 1
5554		local trailDots = createTrailDots(wayPoints, originalEndWaypoint)
5555	
5556		local function removePathBeforePoint(wayPointNumber)
5557			-- kill all trailDots before and at wayPointNumber
5558			for i = #trailDots, 1, -1 do
5559				local trailDot = trailDots[i]
5560				if trailDot.ClosestWayPoint <= wayPointNumber then
5561					trailDot:Destroy()
5562					trailDots[i] = nil
5563				else
5564					break
5565				end
5566			end
5567		end
5568	
5569		local reiszeTrailDotsUpdateName = "ClickToMoveResizeTrail" ..createPathCount
5570		local function resizeTrailDots()
5571			if #trailDots == 0 then
5572				RunService:UnbindFromRenderStep(reiszeTrailDotsUpdateName)
5573				return
5574			end
5575			local cameraPos = Workspace.CurrentCamera.CFrame.p
5576			for i = 1, #trailDots do
5577				local trailDotImage = trailDots[i].DisplayModel:FindFirstChild("TrailDotImage")
5578				if trailDotImage then
5579					local distanceToCamera = (trailDots[i].DisplayModel.Position - cameraPos).magnitude
5580					trailDotImage.Size = getTrailDotScale(distanceToCamera, TrailDotSize)
5581				end
5582			end
5583		end
5584		RunService:BindToRenderStep(reiszeTrailDotsUpdateName, Enum.RenderPriority.Camera.Value - 1, resizeTrailDots)
5585	
5586		local function removePath()
5587			removePathBeforePoint(#wayPoints)
5588		end
5589	
5590		return removePath, removePathBeforePoint
5591	end
5592	
5593	local lastFailureWaypoint = nil
5594	function ClickToMoveDisplay.DisplayFailureWaypoint(position)
5595		if lastFailureWaypoint then
5596			lastFailureWaypoint:Hide()
5597		end
5598		local failureWaypoint = FailureWaypoint.new(position)
5599		lastFailureWaypoint = failureWaypoint
5600		coroutine.wrap(function()
5601			failureWaypoint:RunFailureTween()
5602			failureWaypoint:Destroy()
5603			failureWaypoint = nil
5604		end)()
5605	end
5606	
5607	function ClickToMoveDisplay.CreateEndWaypoint(position)
5608		return EndWaypoint.new(position)
5609	end
5610	
5611	function ClickToMoveDisplay.PlayFailureAnimation()
5612		local myHumanoid = findPlayerHumanoid()
5613		if myHumanoid then
5614			local animationTrack = getFailureAnimationTrack(myHumanoid)
5615			animationTrack:Play()
5616		end
5617	end
5618	
5619	function ClickToMoveDisplay.CancelFailureAnimation()
5620		if lastFailureAnimationTrack ~= nil and lastFailureAnimationTrack.IsPlaying then
5621			lastFailureAnimationTrack:Stop()
5622		end
5623	end
5624	
5625	function ClickToMoveDisplay.SetWaypointTexture(texture)
5626		TrailDotIcon = texture
5627		TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
5628	end
5629	
5630	function ClickToMoveDisplay.GetWaypointTexture()
5631		return TrailDotIcon
5632	end
5633	
5634	function ClickToMoveDisplay.SetWaypointRadius(radius)
5635		TrailDotSize = Vector2.new(radius, radius)
5636		TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
5637	end
5638	
5639	function ClickToMoveDisplay.GetWaypointRadius()
5640		return TrailDotSize.X
5641	end
5642	
5643	function ClickToMoveDisplay.SetEndWaypointTexture(texture)
5644		EndWaypointIcon = texture
5645		TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
5646	end
5647	
5648	function ClickToMoveDisplay.GetEndWaypointTexture()
5649		return EndWaypointIcon
5650	end
5651	
5652	function ClickToMoveDisplay.SetWaypointsAlwaysOnTop(alwaysOnTop)
5653		WaypointsAlwaysOnTop = alwaysOnTop
5654		TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
5655	end
5656	
5657	function ClickToMoveDisplay.GetWaypointsAlwaysOnTop()
5658		return WaypointsAlwaysOnTop
5659	end
5660	
5661	return ClickToMoveDisplay
5662end
5663
5664function _BaseCharacterController()
5665
5666	local ZERO_VECTOR3 = Vector3.new(0,0,0)
5667	
5668	--[[ The Module ]]--
5669	local BaseCharacterController = {}
5670	BaseCharacterController.__index = BaseCharacterController
5671	
5672	function BaseCharacterController.new()
5673		local self = setmetatable({}, BaseCharacterController)
5674		self.enabled = false
5675		self.moveVector = ZERO_VECTOR3
5676		self.moveVectorIsCameraRelative = true
5677		self.isJumping = false
5678		return self
5679	end
5680	
5681	function BaseCharacterController:OnRenderStepped(dt)
5682		-- By default, nothing to do
5683	end
5684	
5685	function BaseCharacterController:GetMoveVector()
5686		return self.moveVector
5687	end
5688	
5689	function BaseCharacterController:IsMoveVectorCameraRelative()
5690		return self.moveVectorIsCameraRelative
5691	end
5692	
5693	function BaseCharacterController:GetIsJumping()
5694		return self.isJumping
5695	end
5696	
5697	-- Override in derived classes to set self.enabled and return boolean indicating
5698	-- whether Enable/Disable was successful. Return true if controller is already in the requested state.
5699	function BaseCharacterController:Enable(enable)
5700		error("BaseCharacterController:Enable must be overridden in derived classes and should not be called.")
5701		return false
5702	end
5703	
5704	return BaseCharacterController
5705end
5706
5707function _VehicleController()
5708	local ContextActionService = game:GetService("ContextActionService")
5709	
5710	--[[ Constants ]]--
5711	-- Set this to true if you want to instead use the triggers for the throttle
5712	local useTriggersForThrottle = true
5713	-- Also set this to true if you want the thumbstick to not affect throttle, only triggers when a gamepad is conected
5714	local onlyTriggersForThrottle = false
5715	local ZERO_VECTOR3 = Vector3.new(0,0,0)
5716	
5717	local AUTO_PILOT_DEFAULT_MAX_STEERING_ANGLE = 35
5718	
5719	
5720	-- Note that VehicleController does not derive from BaseCharacterController, it is a special case
5721	local VehicleController = {}
5722	VehicleController.__index = VehicleController
5723	
5724	function VehicleController.new(CONTROL_ACTION_PRIORITY)
5725		local self = setmetatable({}, VehicleController)
5726	
5727		self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY
5728	
5729		self.enabled = false
5730		self.vehicleSeat = nil
5731		self.throttle = 0
5732		self.steer = 0
5733	
5734		self.acceleration = 0
5735		self.decceleration = 0
5736		self.turningRight = 0
5737		self.turningLeft = 0
5738	
5739		self.vehicleMoveVector = ZERO_VECTOR3
5740	
5741		self.autoPilot = {}
5742		self.autoPilot.MaxSpeed = 0
5743		self.autoPilot.MaxSteeringAngle = 0
5744	
5745		return self
5746	end
5747	
5748	function VehicleController:BindContextActions()
5749		if useTriggersForThrottle then
5750			ContextActionService:BindActionAtPriority("throttleAccel", (function(actionName, inputState, inputObject)
5751				self:OnThrottleAccel(actionName, inputState, inputObject)
5752				return Enum.ContextActionResult.Pass
5753			end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.ButtonR2)
5754			ContextActionService:BindActionAtPriority("throttleDeccel", (function(actionName, inputState, inputObject)
5755				self:OnThrottleDeccel(actionName, inputState, inputObject)
5756				return Enum.ContextActionResult.Pass
5757			end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.ButtonL2)
5758		end
5759		ContextActionService:BindActionAtPriority("arrowSteerRight", (function(actionName, inputState, inputObject)
5760			self:OnSteerRight(actionName, inputState, inputObject)
5761			return Enum.ContextActionResult.Pass
5762		end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.Right)
5763		ContextActionService:BindActionAtPriority("arrowSteerLeft", (function(actionName, inputState, inputObject)
5764			self:OnSteerLeft(actionName, inputState, inputObject)
5765			return Enum.ContextActionResult.Pass
5766		end), false, self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.Left)
5767	end
5768	
5769	function VehicleController:Enable(enable, vehicleSeat)
5770		if enable == self.enabled and vehicleSeat == self.vehicleSeat then
5771			return
5772		end
5773	
5774		self.enabled = enable
5775		self.vehicleMoveVector = ZERO_VECTOR3
5776	
5777		if enable then
5778			if vehicleSeat then
5779				self.vehicleSeat = vehicleSeat
5780	
5781				self:SetupAutoPilot()
5782				self:BindContextActions()
5783			end
5784		else
5785			if useTriggersForThrottle then
5786				ContextActionService:UnbindAction("throttleAccel")
5787				ContextActionService:UnbindAction("throttleDeccel")
5788			end
5789			ContextActionService:UnbindAction("arrowSteerRight")
5790			ContextActionService:UnbindAction("arrowSteerLeft")
5791			self.vehicleSeat = nil
5792		end
5793	end
5794	
5795	function VehicleController:OnThrottleAccel(actionName, inputState, inputObject)
5796		if inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
5797			self.acceleration = 0
5798		else
5799			self.acceleration = -1
5800		end
5801		self.throttle = self.acceleration + self.decceleration
5802	end
5803	
5804	function VehicleController:OnThrottleDeccel(actionName, inputState, inputObject)
5805		if inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
5806			self.decceleration = 0
5807		else
5808			self.decceleration = 1
5809		end
5810		self.throttle = self.acceleration + self.decceleration
5811	end
5812	
5813	function VehicleController:OnSteerRight(actionName, inputState, inputObject)
5814		if inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
5815			self.turningRight = 0
5816		else
5817			self.turningRight = 1
5818		end
5819		self.steer = self.turningRight + self.turningLeft
5820	end
5821	
5822	function VehicleController:OnSteerLeft(actionName, inputState, inputObject)
5823		if inputState == Enum.UserInputState.End or inputState == Enum.UserInputState.Cancel then
5824			self.turningLeft = 0
5825		else
5826			self.turningLeft = -1
5827		end
5828		self.steer = self.turningRight + self.turningLeft
5829	end
5830	
5831	-- Call this from a function bound to Renderstep with Input Priority
5832	function VehicleController:Update(moveVector, cameraRelative, usingGamepad)
5833		if self.vehicleSeat then
5834			if cameraRelative then
5835				-- This is the default steering mode
5836				moveVector = moveVector + Vector3.new(self.steer, 0, self.throttle)
5837				if usingGamepad and onlyTriggersForThrottle and useTriggersForThrottle then
5838					self.vehicleSeat.ThrottleFloat = -self.throttle
5839				else
5840					self.vehicleSeat.ThrottleFloat = -moveVector.Z
5841				end
5842				self.vehicleSeat.SteerFloat = moveVector.X
5843	
5844				return moveVector, true
5845			else
5846				-- This is the path following mode
5847				local localMoveVector = self.vehicleSeat.Occupant.RootPart.CFrame:VectorToObjectSpace(moveVector)
5848	
5849				self.vehicleSeat.ThrottleFloat = self:ComputeThrottle(localMoveVector)
5850				self.vehicleSeat.SteerFloat = self:ComputeSteer(localMoveVector)
5851	
5852				return ZERO_VECTOR3, true
5853			end
5854		end
5855		return moveVector, false
5856	end
5857	
5858	function VehicleController:ComputeThrottle(localMoveVector)
5859		if localMoveVector ~= ZERO_VECTOR3 then
5860			local throttle = -localMoveVector.Z
5861			return throttle
5862		else
5863			return 0.0
5864		end
5865	end
5866	
5867	function VehicleController:ComputeSteer(localMoveVector)
5868		if localMoveVector ~= ZERO_VECTOR3 then
5869			local steerAngle = -math.atan2(-localMoveVector.x, -localMoveVector.z) * (180 / math.pi)
5870			return steerAngle / self.autoPilot.MaxSteeringAngle
5871		else
5872			return 0.0
5873		end
5874	end
5875	
5876	function VehicleController:SetupAutoPilot()
5877		-- Setup default
5878		self.autoPilot.MaxSpeed = self.vehicleSeat.MaxSpeed
5879		self.autoPilot.MaxSteeringAngle = AUTO_PILOT_DEFAULT_MAX_STEERING_ANGLE
5880	
5881		-- VehicleSeat should have a MaxSteeringAngle as well.
5882		-- Or we could look for a child "AutoPilotConfigModule" to find these values
5883		-- Or allow developer to set them through the API as like the CLickToMove customization API
5884	end
5885	
5886	return VehicleController
5887end
5888
5889function _TouchJump()
5890	
5891	local Players = game:GetService("Players")
5892	local GuiService = game:GetService("GuiService")
5893	
5894	--[[ Constants ]]--
5895	local TOUCH_CONTROL_SHEET = "rbxasset://textures/ui/Input/TouchControlsSheetV2.png"
5896	
5897	--[[ The Module ]]--
5898	local BaseCharacterController = _BaseCharacterController()
5899	local TouchJump = setmetatable({}, BaseCharacterController)
5900	TouchJump.__index = TouchJump
5901	
5902	function TouchJump.new()
5903		local self = setmetatable(BaseCharacterController.new(), TouchJump)
5904	
5905		self.parentUIFrame = nil
5906		self.jumpButton = nil
5907		self.characterAddedConn = nil
5908		self.humanoidStateEnabledChangedConn = nil
5909		self.humanoidJumpPowerConn = nil
5910		self.humanoidParentConn = nil
5911		self.externallyEnabled = false
5912		self.jumpPower = 0
5913		self.jumpStateEnabled = true
5914		self.isJumping = false
5915		self.humanoid = nil -- saved reference because property change connections are made using it
5916	
5917		return self
5918	end
5919	
5920	function TouchJump:EnableButton(enable)
5921		if enable then
5922			if not self.jumpButton then
5923				self:Create()
5924			end
5925			local humanoid = Players.LocalPlayer.Character and Players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid")
5926			if humanoid and self.externallyEnabled then
5927				if self.externallyEnabled then
5928					if humanoid.JumpPower > 0 then
5929						self.jumpButton.Visible = true
5930					end
5931				end
5932			end
5933		else
5934			self.jumpButton.Visible = false
5935			self.isJumping = false
5936			self.jumpButton.ImageRectOffset = Vector2.new(1, 146)
5937		end
5938	end
5939	
5940	function TouchJump:UpdateEnabled()
5941		if self.jumpPower > 0 and self.jumpStateEnabled then
5942			self:EnableButton(true)
5943		else
5944			self:EnableButton(false)
5945		end
5946	end
5947	
5948	function TouchJump:HumanoidChanged(prop)
5949		local humanoid = Players.LocalPlayer.Character and Players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid")
5950		if humanoid then
5951			if prop == "JumpPower" then
5952				self.jumpPower =  humanoid.JumpPower
5953				self:UpdateEnabled()
5954			elseif prop == "Parent" then
5955				if not humanoid.Parent then
5956					self.humanoidChangeConn:Disconnect()
5957				end
5958			end
5959		end
5960	end
5961	
5962	function TouchJump:HumanoidStateEnabledChanged(state, isEnabled)
5963		if state == Enum.HumanoidStateType.Jumping then
5964			self.jumpStateEnabled = isEnabled
5965			self:UpdateEnabled()
5966		end
5967	end
5968	
5969	function TouchJump:CharacterAdded(char)
5970		if self.humanoidChangeConn then
5971			self.humanoidChangeConn:Disconnect()
5972			self.humanoidChangeConn = nil
5973		end
5974	
5975		self.humanoid = char:FindFirstChildOfClass("Humanoid")
5976		while not self.humanoid do
5977			char.ChildAdded:wait()
5978			self.humanoid = char:FindFirstChildOfClass("Humanoid")
5979		end
5980	
5981		self.humanoidJumpPowerConn = self.humanoid:GetPropertyChangedSignal("JumpPower"):Connect(function()
5982			self.jumpPower =  self.humanoid.JumpPower
5983			self:UpdateEnabled()
5984		end)
5985	
5986		self.humanoidParentConn = self.humanoid:GetPropertyChangedSignal("Parent"):Connect(function()
5987			if not self.humanoid.Parent then
5988				self.humanoidJumpPowerConn:Disconnect()
5989				self.humanoidJumpPowerConn = nil
5990				self.humanoidParentConn:Disconnect()
5991				self.humanoidParentConn = nil
5992			end
5993		end)
5994	
5995		self.humanoidStateEnabledChangedConn = self.humanoid.StateEnabledChanged:Connect(function(state, enabled)
5996			self:HumanoidStateEnabledChanged(state, enabled)
5997		end)
5998	
5999		self.jumpPower = self.humanoid.JumpPower
6000		self.jumpStateEnabled = self.humanoid:GetStateEnabled(Enum.HumanoidStateType.Jumping)
6001		self:UpdateEnabled()
6002	end
6003	
6004	function TouchJump:SetupCharacterAddedFunction()
6005		self.characterAddedConn = Players.LocalPlayer.CharacterAdded:Connect(function(char)
6006			self:CharacterAdded(char)
6007		end)
6008		if Players.LocalPlayer.Character then
6009			self:CharacterAdded(Players.LocalPlayer.Character)
6010		end
6011	end
6012	
6013	function TouchJump:Enable(enable, parentFrame)
6014		if parentFrame then
6015			self.parentUIFrame = parentFrame
6016		end
6017		self.externallyEnabled = enable
6018		self:EnableButton(enable)
6019	end
6020	
6021	function TouchJump:Create()
6022		if not self.parentUIFrame then
6023			return
6024		end
6025	
6026		if self.jumpButton then
6027			self.jumpButton:Destroy()
6028			self.jumpButton = nil
6029		end
6030	
6031		local minAxis = math.min(self.parentUIFrame.AbsoluteSize.x, self.parentUIFrame.AbsoluteSize.y)
6032		local isSmallScreen = minAxis <= 500
6033		local jumpButtonSize = isSmallScreen and 70 or 120
6034	
6035		self.jumpButton = Instance.new("ImageButton")
6036		self.jumpButton.Name = "JumpButton"
6037		self.jumpButton.Visible = false
6038		self.jumpButton.BackgroundTransparency = 1
6039		self.jumpButton.Image = TOUCH_CONTROL_SHEET
6040		self.jumpButton.ImageRectOffset = Vector2.new(1, 146)
6041		self.jumpButton.ImageRectSize = Vector2.new(144, 144)
6042		self.jumpButton.Size = UDim2.new(0, jumpButtonSize, 0, jumpButtonSize)
6043	
6044	    self.jumpButton.Position = isSmallScreen and UDim2.new(1, -(jumpButtonSize*1.5-10), 1, -jumpButtonSize - 20) or
6045	        UDim2.new(1, -(jumpButtonSize*1.5-10), 1, -jumpButtonSize * 1.75)
6046	
6047		local touchObject = nil
6048		self.jumpButton.InputBegan:connect(function(inputObject)
6049			--A touch that starts elsewhere on the screen will be sent to a frame's InputBegan event
6050			--if it moves over the frame. So we check that this is actually a new touch (inputObject.UserInputState ~= Enum.UserInputState.Begin)
6051			if touchObject or inputObject.UserInputType ~= Enum.UserInputType.Touch
6052				or inputObject.UserInputState ~= Enum.UserInputState.Begin then
6053				return
6054			end
6055	
6056			touchObject = inputObject
6057			self.jumpButton.ImageRectOffset = Vector2.new(146, 146)
6058			self.isJumping = true
6059		end)
6060	
6061		local OnInputEnded = function()
6062			touchObject = nil
6063			self.isJumping = false
6064			self.jumpButton.ImageRectOffset = Vector2.new(1, 146)
6065		end
6066	
6067		self.jumpButton.InputEnded:connect(function(inputObject)
6068			if inputObject == touchObject then
6069				OnInputEnded()
6070			end
6071		end)
6072	
6073		GuiService.MenuOpened:connect(function()
6074			if touchObject then
6075				OnInputEnded()
6076			end
6077		end)
6078	
6079		if not self.characterAddedConn then
6080			self:SetupCharacterAddedFunction()
6081		end
6082	
6083		self.jumpButton.Parent = self.parentUIFrame
6084	end
6085	
6086	return TouchJump
6087end
6088
6089function _ClickToMoveController()
6090	--[[ Roblox Services ]]--
6091	local UserInputService = game:GetService("UserInputService")
6092	local PathfindingService = game:GetService("PathfindingService")
6093	local Players = game:GetService("Players")
6094	local DebrisService = game:GetService('Debris')
6095	local StarterGui = game:GetService("StarterGui")
6096	local Workspace = game:GetService("Workspace")
6097	local CollectionService = game:GetService("CollectionService")
6098	local GuiService = game:GetService("GuiService")
6099	
6100	--[[ Configuration ]]
6101	local ShowPath = true
6102	local PlayFailureAnimation = true
6103	local UseDirectPath = false
6104	local UseDirectPathForVehicle = true
6105	local AgentSizeIncreaseFactor = 1.0
6106	local UnreachableWaypointTimeout = 8
6107	
6108	--[[ Constants ]]--
6109	local movementKeys = {
6110		[Enum.KeyCode.W] = true;
6111		[Enum.KeyCode.A] = true;
6112		[Enum.KeyCode.S] = true;
6113		[Enum.KeyCode.D] = true;
6114		[Enum.KeyCode.Up] = true;
6115		[Enum.KeyCode.Down] = true;
6116	}
6117	
6118	local FFlagUserNavigationClickToMoveSkipPassedWaypointsSuccess, FFlagUserNavigationClickToMoveSkipPassedWaypointsResult = pcall(function() return UserSettings():IsUserFeatureEnabled("UserNavigationClickToMoveSkipPassedWaypoints") end)
6119	local FFlagUserNavigationClickToMoveSkipPassedWaypoints = FFlagUserNavigationClickToMoveSkipPassedWaypointsSuccess and FFlagUserNavigationClickToMoveSkipPassedWaypointsResult
6120	
6121	local Player = Players.LocalPlayer
6122	
6123	local ClickToMoveDisplay = _ClickToMoveDisplay()
6124	
6125	local ZERO_VECTOR3 = Vector3.new(0,0,0)
6126	local ALMOST_ZERO = 0.000001
6127	
6128	
6129	--------------------------UTIL LIBRARY-------------------------------
6130	local Utility = {}
6131	do
6132		local function FindCharacterAncestor(part)
6133			if part then
6134				local humanoid = part:FindFirstChildOfClass("Humanoid")
6135				if humanoid then
6136					return part, humanoid
6137				else
6138					return FindCharacterAncestor(part.Parent)
6139				end
6140			end
6141		end
6142		Utility.FindCharacterAncestor = FindCharacterAncestor
6143	
6144		local function Raycast(ray, ignoreNonCollidable, ignoreList)
6145			ignoreList = ignoreList or {}
6146			local hitPart, hitPos, hitNorm, hitMat = Workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
6147			if hitPart then
6148				if ignoreNonCollidable and hitPart.CanCollide == false then
6149					-- We always include character parts so a user can click on another character
6150					-- to walk to them.
6151					local _, humanoid = FindCharacterAncestor(hitPart)
6152					if humanoid == nil then
6153						table.insert(ignoreList, hitPart)
6154						return Raycast(ray, ignoreNonCollidable, ignoreList)
6155					end
6156				end
6157				return hitPart, hitPos, hitNorm, hitMat
6158			end
6159			return nil, nil
6160		end
6161		Utility.Raycast = Raycast
6162	end
6163	
6164	local humanoidCache = {}
6165	local function findPlayerHumanoid(player)
6166		local character = player and player.Character
6167		if character then
6168			local resultHumanoid = humanoidCache[player]
6169			if resultHumanoid and resultHumanoid.Parent == character then
6170				return resultHumanoid
6171			else
6172				humanoidCache[player] = nil -- Bust Old Cache
6173				local humanoid = character:FindFirstChildOfClass("Humanoid")
6174				if humanoid then
6175					humanoidCache[player] = humanoid
6176				end
6177				return humanoid
6178			end
6179		end
6180	end
6181	
6182	--------------------------CHARACTER CONTROL-------------------------------
6183	local CurrentIgnoreList
6184	local CurrentIgnoreTag = nil
6185	
6186	local TaggedInstanceAddedConnection = nil
6187	local TaggedInstanceRemovedConnection = nil
6188	
6189	local function GetCharacter()
6190		return Player and Player.Character
6191	end
6192	
6193	local function UpdateIgnoreTag(newIgnoreTag)
6194		if newIgnoreTag == CurrentIgnoreTag then
6195			return
6196		end
6197		if TaggedInstanceAddedConnection then
6198			TaggedInstanceAddedConnection:Disconnect()
6199			TaggedInstanceAddedConnection = nil
6200		end
6201		if TaggedInstanceRemovedConnection then
6202			TaggedInstanceRemovedConnection:Disconnect()
6203			TaggedInstanceRemovedConnection = nil
6204		end
6205		CurrentIgnoreTag = newIgnoreTag
6206		CurrentIgnoreList = {GetCharacter()}
6207		if CurrentIgnoreTag ~= nil then
6208			local ignoreParts = CollectionService:GetTagged(CurrentIgnoreTag)
6209			for _, ignorePart in ipairs(ignoreParts) do
6210				table.insert(CurrentIgnoreList, ignorePart)
6211			end
6212			TaggedInstanceAddedConnection = CollectionService:GetInstanceAddedSignal(
6213				CurrentIgnoreTag):Connect(function(ignorePart)
6214				table.insert(CurrentIgnoreList, ignorePart)
6215			end)
6216			TaggedInstanceRemovedConnection = CollectionService:GetInstanceRemovedSignal(
6217				CurrentIgnoreTag):Connect(function(ignorePart)
6218				for i = 1, #CurrentIgnoreList do
6219					if CurrentIgnoreList[i] == ignorePart then
6220						CurrentIgnoreList[i] = CurrentIgnoreList[#CurrentIgnoreList]
6221						table.remove(CurrentIgnoreList)
6222						break
6223					end
6224				end
6225			end)
6226		end
6227	end
6228	
6229	local function getIgnoreList()
6230		if CurrentIgnoreList then
6231			return CurrentIgnoreList
6232		end
6233		CurrentIgnoreList = {}
6234		table.insert(CurrentIgnoreList, GetCharacter())
6235		return CurrentIgnoreList
6236	end
6237	
6238	-----------------------------------PATHER--------------------------------------
6239	
6240	local function Pather(endPoint, surfaceNormal, overrideUseDirectPath)
6241		local this = {}
6242	
6243		local directPathForHumanoid
6244		local directPathForVehicle
6245		if overrideUseDirectPath ~= nil then
6246			directPathForHumanoid = overrideUseDirectPath
6247			directPathForVehicle = overrideUseDirectPath
6248		else
6249			directPathForHumanoid = UseDirectPath
6250			directPathForVehicle = UseDirectPathForVehicle
6251		end
6252	
6253		this.Cancelled = false
6254		this.Started = false
6255	
6256		this.Finished = Instance.new("BindableEvent")
6257		this.PathFailed = Instance.new("BindableEvent")
6258	
6259		this.PathComputing = false
6260		this.PathComputed = false
6261	
6262		this.OriginalTargetPoint = endPoint
6263		this.TargetPoint = endPoint
6264		this.TargetSurfaceNormal = surfaceNormal
6265	
6266		this.DiedConn = nil
6267		this.SeatedConn = nil
6268		this.BlockedConn = nil
6269		this.TeleportedConn = nil
6270	
6271		this.CurrentPoint = 0
6272	
6273		this.HumanoidOffsetFromPath = ZERO_VECTOR3
6274	
6275		this.CurrentWaypointPosition = nil
6276		this.CurrentWaypointPlaneNormal = ZERO_VECTOR3
6277		this.CurrentWaypointPlaneDistance = 0
6278		this.CurrentWaypointNeedsJump = false;
6279	
6280		this.CurrentHumanoidPosition = ZERO_VECTOR3
6281		this.CurrentHumanoidVelocity = 0
6282	
6283		this.NextActionMoveDirection = ZERO_VECTOR3
6284		this.NextActionJump = false
6285	
6286		this.Timeout = 0
6287	
6288		this.Humanoid = findPlayerHumanoid(Player)
6289		this.OriginPoint = nil
6290		this.AgentCanFollowPath = false
6291		this.DirectPath = false
6292		this.DirectPathRiseFirst = false
6293	
6294		local rootPart = this.Humanoid and this.Humanoid.RootPart
6295		if rootPart then
6296			-- Setup origin
6297			this.OriginPoint = rootPart.CFrame.p
6298	
6299			-- Setup agent
6300			local agentRadius = 2
6301			local agentHeight = 5
6302			local agentCanJump = true
6303	
6304			local seat = this.Humanoid.SeatPart
6305			if seat and seat:IsA("VehicleSeat") then
6306				-- Humanoid is seated on a vehicle
6307				local vehicle = seat:FindFirstAncestorOfClass("Model")
6308				if vehicle then
6309					-- Make sure the PrimaryPart is set to the vehicle seat while we compute the extends.
6310					local tempPrimaryPart = vehicle.PrimaryPart
6311					vehicle.PrimaryPart = seat
6312	
6313					-- For now, only direct path
6314					if directPathForVehicle then
6315						local extents = vehicle:GetExtentsSize()
6316						agentRadius = AgentSizeIncreaseFactor * 0.5 * math.sqrt(extents.X * extents.X + extents.Z * extents.Z)
6317						agentHeight = AgentSizeIncreaseFactor * extents.Y
6318						agentCanJump = false
6319						this.AgentCanFollowPath = true
6320						this.DirectPath = directPathForVehicle
6321					end
6322	
6323					-- Reset PrimaryPart
6324					vehicle.PrimaryPart = tempPrimaryPart
6325				end
6326			else
6327				local extents = GetCharacter():GetExtentsSize()
6328				agentRadius = AgentSizeIncreaseFactor * 0.5 * math.sqrt(extents.X * extents.X + extents.Z * extents.Z)
6329				agentHeight = AgentSizeIncreaseFactor * extents.Y
6330				agentCanJump = (this.Humanoid.JumpPower > 0)
6331				this.AgentCanFollowPath = true
6332				this.DirectPath = directPathForHumanoid
6333				this.DirectPathRiseFirst = this.Humanoid.Sit
6334			end
6335	
6336			-- Build path object
6337			this.pathResult = PathfindingService:CreatePath({AgentRadius = agentRadius, AgentHeight = agentHeight, AgentCanJump = agentCanJump})
6338		end
6339	
6340		function this:Cleanup()
6341			if this.stopTraverseFunc then
6342				this.stopTraverseFunc()
6343				this.stopTraverseFunc = nil
6344			end
6345	
6346			if this.MoveToConn then
6347				this.MoveToConn:Disconnect()
6348				this.MoveToConn = nil
6349			end
6350	
6351			if this.BlockedConn then
6352				this.BlockedConn:Disconnect()
6353				this.BlockedConn = nil
6354			end
6355	
6356			if this.DiedConn then
6357				this.DiedConn:Disconnect()
6358				this.DiedConn = nil
6359			end
6360	
6361			if this.SeatedConn then
6362				this.SeatedConn:Disconnect()
6363				this.SeatedConn = nil
6364			end
6365	
6366			if this.TeleportedConn then
6367				this.TeleportedConn:Disconnect()
6368				this.TeleportedConn = nil
6369			end
6370	
6371			this.Started = false
6372		end
6373	
6374		function this:Cancel()
6375			this.Cancelled = true
6376			this:Cleanup()
6377		end
6378	
6379		function this:IsActive()
6380			return this.AgentCanFollowPath and this.Started and not this.Cancelled
6381		end
6382	
6383		function this:OnPathInterrupted()
6384			-- Stop moving
6385			this.Cancelled = true
6386			this:OnPointReached(false)
6387		end
6388	
6389		function this:ComputePath()
6390			if this.OriginPoint then
6391				if this.PathComputed or this.PathComputing then return end
6392				this.PathComputing = true
6393				if this.AgentCanFollowPath then
6394					if this.DirectPath then
6395						this.pointList = {
6396							PathWaypoint.new(this.OriginPoint, Enum.PathWaypointAction.Walk),
6397							PathWaypoint.new(this.TargetPoint, this.DirectPathRiseFirst and Enum.PathWaypointAction.Jump or Enum.PathWaypointAction.Walk)
6398						}
6399						this.PathComputed = true
6400					else
6401						this.pathResult:ComputeAsync(this.OriginPoint, this.TargetPoint)
6402						this.pointList = this.pathResult:GetWaypoints()
6403						this.BlockedConn = this.pathResult.Blocked:Connect(function(blockedIdx) this:OnPathBlocked(blockedIdx) end)
6404						this.PathComputed = this.pathResult.Status == Enum.PathStatus.Success
6405					end
6406				end
6407				this.PathComputing = false
6408			end
6409		end
6410	
6411		function this:IsValidPath()
6412			this:ComputePath()
6413			return this.PathComputed and this.AgentCanFollowPath
6414		end
6415	
6416		this.Recomputing = false
6417		function this:OnPathBlocked(blockedWaypointIdx)
6418			local pathBlocked = blockedWaypointIdx >= this.CurrentPoint
6419			if not pathBlocked or this.Recomputing then
6420				return
6421			end
6422	
6423			this.Recomputing = true
6424	
6425			if this.stopTraverseFunc then
6426				this.stopTraverseFunc()
6427				this.stopTraverseFunc = nil
6428			end
6429	
6430			this.OriginPoint = this.Humanoid.RootPart.CFrame.p
6431	
6432			this.pathResult:ComputeAsync(this.OriginPoint, this.TargetPoint)
6433			this.pointList = this.pathResult:GetWaypoints()
6434			if #this.pointList > 0 then
6435				this.HumanoidOffsetFromPath = this.pointList[1].Position - this.OriginPoint
6436			end
6437			this.PathComputed = this.pathResult.Status == Enum.PathStatus.Success
6438	
6439			if ShowPath then
6440				this.stopTraverseFunc, this.setPointFunc = ClickToMoveDisplay.CreatePathDisplay(this.pointList)
6441			end
6442			if this.PathComputed then
6443				this.CurrentPoint = 1 -- The first waypoint is always the start location. Skip it.
6444				this:OnPointReached(true) -- Move to first point
6445			else
6446				this.PathFailed:Fire()
6447				this:Cleanup()
6448			end
6449	
6450			this.Recomputing = false
6451		end
6452	
6453		function this:OnRenderStepped(dt)
6454			if this.Started and not this.Cancelled then
6455				-- Check for Timeout (if a waypoint is not reached within the delay, we fail)
6456				this.Timeout = this.Timeout + dt
6457				if this.Timeout > UnreachableWaypointTimeout then
6458					this:OnPointReached(false)
6459					return
6460				end
6461	
6462				-- Get Humanoid position and velocity
6463				this.CurrentHumanoidPosition = this.Humanoid.RootPart.Position + this.HumanoidOffsetFromPath
6464				this.CurrentHumanoidVelocity = this.Humanoid.RootPart.Velocity
6465	
6466				-- Check if it has reached some waypoints
6467				while this.Started and this:IsCurrentWaypointReached() do
6468					this:OnPointReached(true)
6469				end
6470	
6471				-- If still started, update actions
6472				if this.Started then
6473					-- Move action
6474					this.NextActionMoveDirection = this.CurrentWaypointPosition - this.CurrentHumanoidPosition
6475					if this.NextActionMoveDirection.Magnitude > ALMOST_ZERO then
6476						this.NextActionMoveDirection = this.NextActionMoveDirection.Unit
6477					else
6478						this.NextActionMoveDirection = ZERO_VECTOR3
6479					end
6480					-- Jump action
6481					if this.CurrentWaypointNeedsJump then
6482						this.NextActionJump = true
6483						this.CurrentWaypointNeedsJump = false	-- Request jump only once
6484					else
6485						this.NextActionJump = false
6486					end
6487				end
6488			end
6489		end
6490	
6491		function this:IsCurrentWaypointReached()
6492			local reached = false
6493	
6494			-- Check we do have a plane, if not, we consider the waypoint reached
6495			if this.CurrentWaypointPlaneNormal ~= ZERO_VECTOR3 then
6496				-- Compute distance of Humanoid from destination plane
6497				local dist = this.CurrentWaypointPlaneNormal:Dot(this.CurrentHumanoidPosition) - this.CurrentWaypointPlaneDistance
6498				-- Compute the component of the Humanoid velocity that is towards the plane
6499				local velocity = -this.CurrentWaypointPlaneNormal:Dot(this.CurrentHumanoidVelocity)
6500				-- Compute the threshold from the destination plane based on Humanoid velocity
6501				local threshold = math.max(1.0, 0.0625 * velocity)
6502				-- If we are less then threshold in front of the plane (between 0 and threshold) or if we are behing the plane (less then 0), we consider we reached it
6503				reached = dist < threshold
6504			else
6505				reached = true
6506			end
6507	
6508			if reached then
6509				this.CurrentWaypointPosition = nil
6510				this.CurrentWaypointPlaneNormal	= ZERO_VECTOR3
6511				this.CurrentWaypointPlaneDistance = 0
6512			end
6513	
6514			return reached
6515		end
6516	
6517		function this:OnPointReached(reached)
6518	
6519			if reached and not this.Cancelled then
6520				-- First, destroyed the current displayed waypoint
6521				if this.setPointFunc then
6522					this.setPointFunc(this.CurrentPoint)
6523				end
6524	
6525				local nextWaypointIdx = this.CurrentPoint + 1
6526	
6527				if nextWaypointIdx > #this.pointList then
6528					-- End of path reached
6529					if this.stopTraverseFunc then
6530						this.stopTraverseFunc()
6531					end
6532					this.Finished:Fire()
6533					this:Cleanup()
6534				else
6535					local currentWaypoint = this.pointList[this.CurrentPoint]
6536					local nextWaypoint = this.pointList[nextWaypointIdx]
6537	
6538					-- If airborne, only allow to keep moving
6539					-- if nextWaypoint.Action ~= Jump, or path mantains a direction
6540					-- Otherwise, wait until the humanoid gets to the ground
6541					local currentState = this.Humanoid:GetState()
6542					local isInAir = currentState == Enum.HumanoidStateType.FallingDown
6543						or currentState == Enum.HumanoidStateType.Freefall
6544						or currentState == Enum.HumanoidStateType.Jumping
6545	
6546					if isInAir then
6547						local shouldWaitForGround = nextWaypoint.Action == Enum.PathWaypointAction.Jump
6548						if not shouldWaitForGround and this.CurrentPoint > 1 then
6549							local prevWaypoint = this.pointList[this.CurrentPoint - 1]
6550	
6551							local prevDir = currentWaypoint.Position - prevWaypoint.Position
6552							local currDir = nextWaypoint.Position - currentWaypoint.Position
6553	
6554							local prevDirXZ = Vector2.new(prevDir.x, prevDir.z).Unit
6555							local currDirXZ = Vector2.new(currDir.x, currDir.z).Unit
6556	
6557							local THRESHOLD_COS = 0.996 -- ~cos(5 degrees)
6558							shouldWaitForGround = prevDirXZ:Dot(currDirXZ) < THRESHOLD_COS
6559						end
6560	
6561						if shouldWaitForGround then
6562							this.Humanoid.FreeFalling:Wait()
6563	
6564							-- Give time to the humanoid's state to change
6565							-- Otherwise, the jump flag in Humanoid
6566							-- will be reset by the state change
6567							wait(0.1)
6568						end
6569					end
6570	
6571					-- Move to the next point
6572					if FFlagUserNavigationClickToMoveSkipPassedWaypoints then
6573						this:MoveToNextWayPoint(currentWaypoint, nextWaypoint, nextWaypointIdx)
6574					else
6575						if this.setPointFunc then
6576							this.setPointFunc(nextWaypointIdx)
6577						end
6578						if nextWaypoint.Action == Enum.PathWaypointAction.Jump then
6579							this.Humanoid.Jump = true
6580						end
6581						this.Humanoid:MoveTo(nextWaypoint.Position)
6582	
6583						this.CurrentPoint = nextWaypointIdx
6584					end
6585				end
6586			else
6587				this.PathFailed:Fire()
6588				this:Cleanup()
6589			end
6590		end
6591	
6592		function this:MoveToNextWayPoint(currentWaypoint, nextWaypoint, nextWaypointIdx)
6593			-- Build next destination plane
6594			-- (plane normal is perpendicular to the y plane and is from next waypoint towards current one (provided the two waypoints are not at the same location))
6595			-- (plane location is at next waypoint)
6596			this.CurrentWaypointPlaneNormal = currentWaypoint.Position - nextWaypoint.Position
6597			this.CurrentWaypointPlaneNormal = Vector3.new(this.CurrentWaypointPlaneNormal.X, 0, this.CurrentWaypointPlaneNormal.Z)
6598			if this.CurrentWaypointPlaneNormal.Magnitude > ALMOST_ZERO then
6599				this.CurrentWaypointPlaneNormal	= this.CurrentWaypointPlaneNormal.Unit
6600				this.CurrentWaypointPlaneDistance = this.CurrentWaypointPlaneNormal:Dot(nextWaypoint.Position)
6601			else
6602				-- Next waypoint is the same as current waypoint so no plane
6603				this.CurrentWaypointPlaneNormal	= ZERO_VECTOR3
6604				this.CurrentWaypointPlaneDistance = 0
6605			end
6606	
6607			-- Should we jump
6608			this.CurrentWaypointNeedsJump = nextWaypoint.Action == Enum.PathWaypointAction.Jump;
6609	
6610			-- Remember next waypoint position
6611			this.CurrentWaypointPosition = nextWaypoint.Position
6612	
6613			-- Move to next point
6614			this.CurrentPoint = nextWaypointIdx
6615	
6616			-- Finally reset Timeout
6617			this.Timeout = 0
6618		end
6619	
6620		function this:Start(overrideShowPath)
6621			if not this.AgentCanFollowPath then
6622				this.PathFailed:Fire()
6623				return
6624			end
6625	
6626			if this.Started then return end
6627			this.Started = true
6628	
6629			ClickToMoveDisplay.CancelFailureAnimation()
6630	
6631			if ShowPath then
6632				if overrideShowPath == nil or overrideShowPath then
6633					this.stopTraverseFunc, this.setPointFunc = ClickToMoveDisplay.CreatePathDisplay(this.pointList, this.OriginalTargetPoint)
6634				end
6635			end
6636	
6637			if #this.pointList > 0 then
6638				-- Determine the humanoid offset from the path's first point
6639				-- Offset of the first waypoint from the path's origin point
6640				this.HumanoidOffsetFromPath = Vector3.new(0, this.pointList[1].Position.Y - this.OriginPoint.Y, 0)
6641	
6642				-- As well as its current position and velocity
6643				this.CurrentHumanoidPosition = this.Humanoid.RootPart.Position + this.HumanoidOffsetFromPath
6644				this.CurrentHumanoidVelocity = this.Humanoid.RootPart.Velocity
6645	
6646				-- Connect to events
6647				this.SeatedConn = this.Humanoid.Seated:Connect(function(isSeated, seat) this:OnPathInterrupted() end)
6648				this.DiedConn = this.Humanoid.Died:Connect(function() this:OnPathInterrupted() end)
6649				this.TeleportedConn = this.Humanoid.RootPart:GetPropertyChangedSignal("CFrame"):Connect(function() this:OnPathInterrupted() end)
6650	
6651				-- Actually start
6652				this.CurrentPoint = 1 -- The first waypoint is always the start location. Skip it.
6653				this:OnPointReached(true) -- Move to first point
6654			else
6655				this.PathFailed:Fire()
6656				if this.stopTraverseFunc then
6657					this.stopTraverseFunc()
6658				end
6659			end
6660		end
6661	
6662		--We always raycast to the ground in the case that the user clicked a wall.
6663		local offsetPoint = this.TargetPoint + this.TargetSurfaceNormal*1.5
6664		local ray = Ray.new(offsetPoint, Vector3.new(0,-1,0)*50)
6665		local newHitPart, newHitPos = Workspace:FindPartOnRayWithIgnoreList(ray, getIgnoreList())
6666		if newHitPart then
6667			this.TargetPoint = newHitPos
6668		end
6669		this:ComputePath()
6670	
6671		return this
6672	end
6673	
6674	-------------------------------------------------------------------------
6675	
6676	local function CheckAlive()
6677		local humanoid = findPlayerHumanoid(Player)
6678		return humanoid ~= nil and humanoid.Health > 0
6679	end
6680	
6681	local function GetEquippedTool(character)
6682		if character ~= nil then
6683			for _, child in pairs(character:GetChildren()) do
6684				if child:IsA('Tool') then
6685					return child
6686				end
6687			end
6688		end
6689	end
6690	
6691	local ExistingPather = nil
6692	local ExistingIndicator = nil
6693	local PathCompleteListener = nil
6694	local PathFailedListener = nil
6695	
6696	local function CleanupPath()
6697		if ExistingPather then
6698			ExistingPather:Cancel()
6699			ExistingPather = nil
6700		end
6701		if PathCompleteListener then
6702			PathCompleteListener:Disconnect()
6703			PathCompleteListener = nil
6704		end
6705		if PathFailedListener then
6706			PathFailedListener:Disconnect()
6707			PathFailedListener = nil
6708		end
6709		if ExistingIndicator then
6710			ExistingIndicator:Destroy()
6711		end
6712	end
6713	
6714	local function HandleMoveTo(thisPather, hitPt, hitChar, character, overrideShowPath)
6715		if ExistingPather then
6716			CleanupPath()
6717		end
6718		ExistingPather = thisPather
6719		thisPather:Start(overrideShowPath)
6720	
6721		PathCompleteListener = thisPather.Finished.Event:Connect(function()
6722			CleanupPath()
6723			if hitChar then
6724				local currentWeapon = GetEquippedTool(character)
6725				if currentWeapon then
6726					currentWeapon:Activate()
6727				end
6728			end
6729		end)
6730		PathFailedListener = thisPather.PathFailed.Event:Connect(function()
6731			CleanupPath()
6732			if overrideShowPath == nil or overrideShowPath then
6733				local shouldPlayFailureAnim = PlayFailureAnimation and not (ExistingPather and ExistingPather:IsActive())
6734				if shouldPlayFailureAnim then
6735					ClickToMoveDisplay.PlayFailureAnimation()
6736				end
6737				ClickToMoveDisplay.DisplayFailureWaypoint(hitPt)
6738			end
6739		end)
6740	end
6741	
6742	local function ShowPathFailedFeedback(hitPt)
6743		if ExistingPather and ExistingPather:IsActive() then
6744			ExistingPather:Cancel()
6745		end
6746		if PlayFailureAnimation then
6747			ClickToMoveDisplay.PlayFailureAnimation()
6748		end
6749		ClickToMoveDisplay.DisplayFailureWaypoint(hitPt)
6750	end
6751	
6752	function OnTap(tapPositions, goToPoint, wasTouchTap)
6753		-- Good to remember if this is the latest tap event
6754		local camera = Workspace.CurrentCamera
6755		local character = Player.Character
6756	
6757		if not CheckAlive() then return end
6758	
6759		-- This is a path tap position
6760		if #tapPositions == 1 or goToPoint then
6761			if camera then
6762				local unitRay = camera:ScreenPointToRay(tapPositions[1].x, tapPositions[1].y)
6763				local ray = Ray.new(unitRay.Origin, unitRay.Direction*1000)
6764	
6765				local myHumanoid = findPlayerHumanoid(Player)
6766				local hitPart, hitPt, hitNormal = Utility.Raycast(ray, true, getIgnoreList())
6767	
6768				local hitChar, hitHumanoid = Utility.FindCharacterAncestor(hitPart)
6769				if wasTouchTap and hitHumanoid and StarterGui:GetCore("AvatarContextMenuEnabled") then
6770					local clickedPlayer = Players:GetPlayerFromCharacter(hitHumanoid.Parent)
6771					if clickedPlayer then
6772						CleanupPath()
6773						return
6774					end
6775				end
6776				if goToPoint then
6777					hitPt = goToPoint
6778					hitChar = nil
6779				end
6780				if hitPt and character then
6781					-- Clean up current path
6782					CleanupPath()
6783					local thisPather = Pather(hitPt, hitNormal)
6784					if thisPather:IsValidPath() then
6785						HandleMoveTo(thisPather, hitPt, hitChar, character)
6786					else
6787						-- Clean up
6788						thisPather:Cleanup()
6789						-- Feedback here for when we don't have a good path
6790						ShowPathFailedFeedback(hitPt)
6791					end
6792				end
6793			end
6794		elseif #tapPositions >= 2 then
6795			if camera then
6796				-- Do shoot
6797				local currentWeapon = GetEquippedTool(character)
6798				if currentWeapon then
6799					currentWeapon:Activate()
6800				end
6801			end
6802		end
6803	end
6804	
6805	local function DisconnectEvent(event)
6806		if event then
6807			event:Disconnect()
6808		end
6809	end
6810	
6811	--[[ The ClickToMove Controller Class ]]--
6812	local KeyboardController = _Keyboard()
6813	local ClickToMove = setmetatable({}, KeyboardController)
6814	ClickToMove.__index = ClickToMove
6815	
6816	function ClickToMove.new(CONTROL_ACTION_PRIORITY)
6817		local self = setmetatable(KeyboardController.new(CONTROL_ACTION_PRIORITY), ClickToMove)
6818	
6819		self.fingerTouches = {}
6820		self.numUnsunkTouches = 0
6821		-- PC simulation
6822		self.mouse1Down = tick()
6823		self.mouse1DownPos = Vector2.new()
6824		self.mouse2DownTime = tick()
6825		self.mouse2DownPos = Vector2.new()
6826		self.mouse2UpTime = tick()
6827	
6828		self.keyboardMoveVector = ZERO_VECTOR3
6829	
6830		self.tapConn = nil
6831		self.inputBeganConn = nil
6832		self.inputChangedConn = nil
6833		self.inputEndedConn = nil
6834		self.humanoidDiedConn = nil
6835		self.characterChildAddedConn = nil
6836		self.onCharacterAddedConn = nil
6837		self.characterChildRemovedConn = nil
6838		self.renderSteppedConn = nil
6839		self.menuOpenedConnection = nil
6840	
6841		self.running = false
6842	
6843		self.wasdEnabled = false
6844	
6845		return self
6846	end
6847	
6848	function ClickToMove:DisconnectEvents()
6849		DisconnectEvent(self.tapConn)
6850		DisconnectEvent(self.inputBeganConn)
6851		DisconnectEvent(self.inputChangedConn)
6852		DisconnectEvent(self.inputEndedConn)
6853		DisconnectEvent(self.humanoidDiedConn)
6854		DisconnectEvent(self.characterChildAddedConn)
6855		DisconnectEvent(self.onCharacterAddedConn)
6856		DisconnectEvent(self.renderSteppedConn)
6857		DisconnectEvent(self.characterChildRemovedConn)
6858		DisconnectEvent(self.menuOpenedConnection)
6859	end
6860	
6861	function ClickToMove:OnTouchBegan(input, processed)
6862		if self.fingerTouches[input] == nil and not processed then
6863			self.numUnsunkTouches = self.numUnsunkTouches + 1
6864		end
6865		self.fingerTouches[input] = processed
6866	end
6867	
6868	function ClickToMove:OnTouchChanged(input, processed)
6869		if self.fingerTouches[input] == nil then
6870			self.fingerTouches[input] = processed
6871			if not processed then
6872				self.numUnsunkTouches = self.numUnsunkTouches + 1
6873			end
6874		end
6875	end
6876	
6877	function ClickToMove:OnTouchEnded(input, processed)
6878		if self.fingerTouches[input] ~= nil and self.fingerTouches[input] == false then
6879			self.numUnsunkTouches = self.numUnsunkTouches - 1
6880		end
6881		self.fingerTouches[input] = nil
6882	end
6883	
6884	
6885	function ClickToMove:OnCharacterAdded(character)
6886		self:DisconnectEvents()
6887	
6888		self.inputBeganConn = UserInputService.InputBegan:Connect(function(input, processed)
6889			if input.UserInputType == Enum.UserInputType.Touch then
6890				self:OnTouchBegan(input, processed)
6891			end
6892	
6893			-- Cancel path when you use the keyboard controls if wasd is enabled.
6894			if self.wasdEnabled and processed == false and input.UserInputType == Enum.UserInputType.Keyboard
6895				and movementKeys[input.KeyCode] then
6896				CleanupPath()
6897				ClickToMoveDisplay.CancelFailureAnimation()
6898			end
6899			if input.UserInputType == Enum.UserInputType.MouseButton1 then
6900				self.mouse1DownTime = tick()
6901				self.mouse1DownPos = input.Position
6902			end
6903			if input.UserInputType == Enum.UserInputType.MouseButton2 then
6904				self.mouse2DownTime = tick()
6905				self.mouse2DownPos = input.Position
6906			end
6907		end)
6908	
6909		self.inputChangedConn = UserInputService.InputChanged:Connect(function(input, processed)
6910			if input.UserInputType == Enum.UserInputType.Touch then
6911				self:OnTouchChanged(input, processed)
6912			end
6913		end)
6914	
6915		self.inputEndedConn = UserInputService.InputEnded:Connect(function(input, processed)
6916			if input.UserInputType == Enum.UserInputType.Touch then
6917				self:OnTouchEnded(input, processed)
6918			end
6919	
6920			if input.UserInputType == Enum.UserInputType.MouseButton2 then
6921				self.mouse2UpTime = tick()
6922				local currPos = input.Position
6923				-- We allow click to move during path following or if there is no keyboard movement
6924				local allowed = ExistingPather or self.keyboardMoveVector.Magnitude <= 0
6925				if self.mouse2UpTime - self.mouse2DownTime < 0.25 and (currPos - self.mouse2DownPos).magnitude < 5 and allowed then
6926					local positions = {currPos}
6927					OnTap(positions)
6928				end
6929			end
6930		end)
6931	
6932		self.tapConn = UserInputService.TouchTap:Connect(function(touchPositions, processed)
6933			if not processed then
6934				OnTap(touchPositions, nil, true)
6935			end
6936		end)
6937	
6938		self.menuOpenedConnection = GuiService.MenuOpened:Connect(function()
6939			CleanupPath()
6940		end)
6941	
6942		local function OnCharacterChildAdded(child)
6943			if UserInputService.TouchEnabled then
6944				if child:IsA('Tool') then
6945					child.ManualActivationOnly = true
6946				end
6947			end
6948			if child:IsA('Humanoid') then
6949				DisconnectEvent(self.humanoidDiedConn)
6950				self.humanoidDiedConn = child.Died:Connect(function()
6951					if ExistingIndicator then
6952						DebrisService:AddItem(ExistingIndicator.Model, 1)
6953					end
6954				end)
6955			end
6956		end
6957	
6958		self.characterChildAddedConn = character.ChildAdded:Connect(function(child)
6959			OnCharacterChildAdded(child)
6960		end)
6961		self.characterChildRemovedConn = character.ChildRemoved:Connect(function(child)
6962			if UserInputService.TouchEnabled then
6963				if child:IsA('Tool') then
6964					child.ManualActivationOnly = false
6965				end
6966			end
6967		end)
6968		for _, child in pairs(character:GetChildren()) do
6969			OnCharacterChildAdded(child)
6970		end
6971	end
6972	
6973	function ClickToMove:Start()
6974		self:Enable(true)
6975	end
6976	
6977	function ClickToMove:Stop()
6978		self:Enable(false)
6979	end
6980	
6981	function ClickToMove:CleanupPath()
6982		CleanupPath()
6983	end
6984	
6985	function ClickToMove:Enable(enable, enableWASD, touchJumpController)
6986		if enable then
6987			if not self.running then
6988				if Player.Character then -- retro-listen
6989					self:OnCharacterAdded(Player.Character)
6990				end
6991				self.onCharacterAddedConn = Player.CharacterAdded:Connect(function(char)
6992					self:OnCharacterAdded(char)
6993				end)
6994				self.running = true
6995			end
6996			self.touchJumpController = touchJumpController
6997			if self.touchJumpController then
6998				self.touchJumpController:Enable(self.jumpEnabled)
6999			end
7000		else
7001			if self.running then
7002				self:DisconnectEvents()
7003				CleanupPath()
7004				-- Restore tool activation on shutdown
7005				if UserInputService.TouchEnabled then
7006					local character = Player.Character
7007					if character then
7008						for _, child in pairs(character:GetChildren()) do
7009							if child:IsA('Tool') then
7010								child.ManualActivationOnly = false
7011							end
7012						end
7013					end
7014				end
7015				self.running = false
7016			end
7017			if self.touchJumpController and not self.jumpEnabled then
7018				self.touchJumpController:Enable(true)
7019			end
7020			self.touchJumpController = nil
7021		end
7022	
7023		-- Extension for initializing Keyboard input as this class now derives from Keyboard
7024		if UserInputService.KeyboardEnabled and enable ~= self.enabled then
7025	
7026			self.forwardValue  = 0
7027			self.backwardValue = 0
7028			self.leftValue = 0
7029			self.rightValue = 0
7030	
7031			self.moveVector = ZERO_VECTOR3
7032	
7033			if enable then
7034				self:BindContextActions()
7035				self:ConnectFocusEventListeners()
7036			else
7037				self:UnbindContextActions()
7038				self:DisconnectFocusEventListeners()
7039			end
7040		end
7041	
7042		self.wasdEnabled = enable and enableWASD or false
7043		self.enabled = enable
7044	end
7045	
7046	function ClickToMove:OnRenderStepped(dt)
7047		-- Reset jump
7048		self.isJumping = false
7049	
7050		-- Handle Pather
7051		if ExistingPather then
7052			-- Let the Pather update
7053			ExistingPather:OnRenderStepped(dt)
7054	
7055			-- If we still have a Pather, set the resulting actions
7056			if ExistingPather then
7057				-- Setup move (NOT relative to camera)
7058				self.moveVector = ExistingPather.NextActionMoveDirection
7059				self.moveVectorIsCameraRelative = false
7060	
7061				-- Setup jump (but do NOT prevent the base Keayboard class from requesting jumps as well)
7062				if ExistingPather.NextActionJump then
7063					self.isJumping = true
7064				end
7065			else
7066				self.moveVector = self.keyboardMoveVector
7067				self.moveVectorIsCameraRelative = true
7068			end
7069		else
7070			self.moveVector = self.keyboardMoveVector
7071			self.moveVectorIsCameraRelative = true
7072		end
7073	
7074		-- Handle Keyboard's jump
7075		if self.jumpRequested then
7076			self.isJumping = true
7077		end
7078	end
7079	
7080	-- Overrides Keyboard:UpdateMovement(inputState) to conditionally consider self.wasdEnabled and let OnRenderStepped handle the movement
7081	function ClickToMove:UpdateMovement(inputState)
7082		if inputState == Enum.UserInputState.Cancel then
7083			self.keyboardMoveVector = ZERO_VECTOR3
7084		elseif self.wasdEnabled then
7085			self.keyboardMoveVector = Vector3.new(self.leftValue + self.rightValue, 0, self.forwardValue + self.backwardValue)
7086		end
7087	end
7088	
7089	-- Overrides Keyboard:UpdateJump() because jump is handled in OnRenderStepped
7090	function ClickToMove:UpdateJump()
7091		-- Nothing to do (handled in OnRenderStepped)
7092	end
7093	
7094	--Public developer facing functions
7095	function ClickToMove:SetShowPath(value)
7096		ShowPath = value
7097	end
7098	
7099	function ClickToMove:GetShowPath()
7100		return ShowPath
7101	end
7102	
7103	function ClickToMove:SetWaypointTexture(texture)
7104		ClickToMoveDisplay.SetWaypointTexture(texture)
7105	end
7106	
7107	function ClickToMove:GetWaypointTexture()
7108		return ClickToMoveDisplay.GetWaypointTexture()
7109	end
7110	
7111	function ClickToMove:SetWaypointRadius(radius)
7112		ClickToMoveDisplay.SetWaypointRadius(radius)
7113	end
7114	
7115	function ClickToMove:GetWaypointRadius()
7116		return ClickToMoveDisplay.GetWaypointRadius()
7117	end
7118	
7119	function ClickToMove:SetEndWaypointTexture(texture)
7120		ClickToMoveDisplay.SetEndWaypointTexture(texture)
7121	end
7122	
7123	function ClickToMove:GetEndWaypointTexture()
7124		return ClickToMoveDisplay.GetEndWaypointTexture()
7125	end
7126	
7127	function ClickToMove:SetWaypointsAlwaysOnTop(alwaysOnTop)
7128		ClickToMoveDisplay.SetWaypointsAlwaysOnTop(alwaysOnTop)
7129	end
7130	
7131	function ClickToMove:GetWaypointsAlwaysOnTop()
7132		return ClickToMoveDisplay.GetWaypointsAlwaysOnTop()
7133	end
7134	
7135	function ClickToMove:SetFailureAnimationEnabled(enabled)
7136		PlayFailureAnimation = enabled
7137	end
7138	
7139	function ClickToMove:GetFailureAnimationEnabled()
7140		return PlayFailureAnimation
7141	end
7142	
7143	function ClickToMove:SetIgnoredPartsTag(tag)
7144		UpdateIgnoreTag(tag)
7145	end
7146	
7147	function ClickToMove:GetIgnoredPartsTag()
7148		return CurrentIgnoreTag
7149	end
7150	
7151	function ClickToMove:SetUseDirectPath(directPath)
7152		UseDirectPath = directPath
7153	end
7154	
7155	function ClickToMove:GetUseDirectPath()
7156		return UseDirectPath
7157	end
7158	
7159	function ClickToMove:SetAgentSizeIncreaseFactor(increaseFactorPercent)
7160		AgentSizeIncreaseFactor = 1.0 + (increaseFactorPercent / 100.0)
7161	end
7162	
7163	function ClickToMove:GetAgentSizeIncreaseFactor()
7164		return (AgentSizeIncreaseFactor - 1.0) * 100.0
7165	end
7166	
7167	function ClickToMove:SetUnreachableWaypointTimeout(timeoutInSec)
7168		UnreachableWaypointTimeout = timeoutInSec
7169	end
7170	
7171	function ClickToMove:GetUnreachableWaypointTimeout()
7172		return UnreachableWaypointTimeout
7173	end
7174	
7175	function ClickToMove:SetUserJumpEnabled(jumpEnabled)
7176		self.jumpEnabled = jumpEnabled
7177		if self.touchJumpController then
7178			self.touchJumpController:Enable(jumpEnabled)
7179		end
7180	end
7181	
7182	function ClickToMove:GetUserJumpEnabled()
7183		return self.jumpEnabled
7184	end
7185	
7186	function ClickToMove:MoveTo(position, showPath, useDirectPath)
7187		local character = Player.Character
7188		if character == nil then
7189			return false
7190		end
7191		local thisPather = Pather(position, Vector3.new(0, 1, 0), useDirectPath)
7192		if thisPather and thisPather:IsValidPath() then
7193			HandleMoveTo(thisPather, position, nil, character, showPath)
7194			return true
7195		end
7196		return false
7197	end
7198	
7199	return ClickToMove
7200end
7201
7202function _TouchThumbstick()
7203	local Players = game:GetService("Players")
7204	local GuiService = game:GetService("GuiService")
7205	local UserInputService = game:GetService("UserInputService")
7206	--[[ Constants ]]--
7207	local ZERO_VECTOR3 = Vector3.new(0,0,0)
7208	local TOUCH_CONTROL_SHEET = "rbxasset://textures/ui/TouchControlsSheet.png"
7209	--[[ The Module ]]--
7210	local BaseCharacterController = _BaseCharacterController()
7211	local TouchThumbstick = setmetatable({}, BaseCharacterController)
7212	TouchThumbstick.__index = TouchThumbstick
7213	function TouchThumbstick.new()
7214		local self = setmetatable(BaseCharacterController.new(), TouchThumbstick)
7215		
7216		self.isFollowStick = false
7217		
7218		self.thumbstickFrame = nil
7219		self.moveTouchObject = nil
7220		self.onTouchMovedConn = nil
7221		self.onTouchEndedConn = nil
7222		self.screenPos = nil
7223		self.stickImage = nil
7224		self.thumbstickSize = nil -- Float
7225		
7226		return self
7227	end
7228	function TouchThumbstick:Enable(enable, uiParentFrame)
7229		if enable == nil then return false end			-- If nil, return false (invalid argument)
7230		enable = enable and true or false				-- Force anything non-nil to boolean before comparison
7231		if self.enabled == enable then return true end	-- If no state change, return true indicating already in requested state
7232		
7233		self.moveVector = ZERO_VECTOR3
7234		self.isJumping = false
7235		
7236		if enable then
7237			-- Enable
7238			if not self.thumbstickFrame then
7239				self:Create(uiParentFrame)
7240			end
7241			self.thumbstickFrame.Visible = true
7242		else 
7243			-- Disable
7244			self.thumbstickFrame.Visible = false
7245			self:OnInputEnded()
7246		end
7247		self.enabled = enable
7248	end
7249	function TouchThumbstick:OnInputEnded()
7250		self.thumbstickFrame.Position = self.screenPos
7251		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)
7252		
7253		self.moveVector = ZERO_VECTOR3
7254		self.isJumping = false
7255		self.thumbstickFrame.Position = self.screenPos
7256		self.moveTouchObject = nil
7257	end
7258	function TouchThumbstick:Create(parentFrame)
7259		
7260		if self.thumbstickFrame then
7261			self.thumbstickFrame:Destroy()
7262			self.thumbstickFrame = nil
7263			if self.onTouchMovedConn then
7264				self.onTouchMovedConn:Disconnect()
7265				self.onTouchMovedConn = nil
7266			end
7267			if self.onTouchEndedConn then
7268				self.onTouchEndedConn:Disconnect()
7269				self.onTouchEndedConn = nil
7270			end
7271		end
7272		
7273		local minAxis = math.min(parentFrame.AbsoluteSize.x, parentFrame.AbsoluteSize.y)
7274		local isSmallScreen = minAxis <= 500
7275		self.thumbstickSize = isSmallScreen and 70 or 120
7276		self.screenPos = isSmallScreen and UDim2.new(0, (self.thumbstickSize/2) - 10, 1, -self.thumbstickSize - 20) or
7277			UDim2.new(0, self.thumbstickSize/2, 1, -self.thumbstickSize * 1.75)
7278			
7279		self.thumbstickFrame = Instance.new("Frame")
7280		self.thumbstickFrame.Name = "ThumbstickFrame"
7281		self.thumbstickFrame.Active = true
7282		self.thumbstickFrame.Visible = false
7283		self.thumbstickFrame.Size = UDim2.new(0, self.thumbstickSize, 0, self.thumbstickSize)
7284		self.thumbstickFrame.Position = self.screenPos
7285		self.thumbstickFrame.BackgroundTransparency = 1
7286		
7287		local outerImage = Instance.new("ImageLabel")
7288		outerImage.Name = "OuterImage"
7289		outerImage.Image = TOUCH_CONTROL_SHEET
7290		outerImage.ImageRectOffset = Vector2.new()
7291		outerImage.ImageRectSize = Vector2.new(220, 220)
7292		outerImage.BackgroundTransparency = 1
7293		outerImage.Size = UDim2.new(0, self.thumbstickSize, 0, self.thumbstickSize)
7294		outerImage.Position = UDim2.new(0, 0, 0, 0)
7295		outerImage.Parent = self.thumbstickFrame
7296		
7297		self.stickImage = Instance.new("ImageLabel")
7298		self.stickImage.Name = "StickImage"
7299		self.stickImage.Image = TOUCH_CONTROL_SHEET
7300		self.stickImage.ImageRectOffset = Vector2.new(220, 0)
7301		self.stickImage.ImageRectSize = Vector2.new(111, 111)
7302		self.stickImage.BackgroundTransparency = 1
7303		self.stickImage.Size = UDim2.new(0, self.thumbstickSize/2, 0, self.thumbstickSize/2)
7304		self.stickImage.Position = UDim2.new(0, self.thumbstickSize/2 - self.thumbstickSize/4, 0, self.thumbstickSize/2 - self.thumbstickSize/4)
7305		self.stickImage.ZIndex = 2
7306		self.stickImage.Parent = self.thumbstickFrame
7307		
7308		local centerPosition = nil
7309		local deadZone = 0.05
7310		
7311		local function DoMove(direction)
7312			
7313			local currentMoveVector = direction / (self.thumbstickSize/2)
7314			
7315			-- Scaled Radial Dead Zone
7316			local inputAxisMagnitude = currentMoveVector.magnitude
7317			if inputAxisMagnitude < deadZone then
7318				currentMoveVector = Vector3.new()
7319			else
7320				currentMoveVector = currentMoveVector.unit * ((inputAxisMagnitude - deadZone) / (1 - deadZone))
7321				-- NOTE: Making currentMoveVector a unit vector will cause the player to instantly go max speed
7322				-- must check for zero length vector is using unit
7323				currentMoveVector = Vector3.new(currentMoveVector.x, 0, currentMoveVector.y)
7324			end
7325			
7326			self.moveVector = currentMoveVector
7327		end
7328		
7329		local function MoveStick(pos)
7330			local relativePosition = Vector2.new(pos.x - centerPosition.x, pos.y - centerPosition.y)
7331			local length = relativePosition.magnitude
7332			local maxLength = self.thumbstickFrame.AbsoluteSize.x/2
7333			if self.isFollowStick and length > maxLength then
7334				local offset = relativePosition.unit * maxLength
7335				self.thumbstickFrame.Position = UDim2.new(
7336					0, pos.x - self.thumbstickFrame.AbsoluteSize.x/2 - offset.x,
7337					0, pos.y - self.thumbstickFrame.AbsoluteSize.y/2 - offset.y)
7338			else
7339				length = math.min(length, maxLength)
7340				relativePosition = relativePosition.unit * length
7341			end
7342			self.stickImage.Position = UDim2.new(0, relativePosition.x + self.stickImage.AbsoluteSize.x/2, 0, relativePosition.y + self.stickImage.AbsoluteSize.y/2)
7343		end
7344		
7345		-- input connections
7346		self.thumbstickFrame.InputBegan:Connect(function(inputObject)
7347			--A touch that starts elsewhere on the screen will be sent to a frame's InputBegan event
7348			--if it moves over the frame. So we check that this is actually a new touch (inputObject.UserInputState ~= Enum.UserInputState.Begin)
7349			if self.moveTouchObject or inputObject.UserInputType ~= Enum.UserInputType.Touch
7350				or inputObject.UserInputState ~= Enum.UserInputState.Begin then
7351				return
7352			end
7353			
7354			self.moveTouchObject = inputObject
7355			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)
7356			centerPosition = Vector2.new(self.thumbstickFrame.AbsolutePosition.x + self.thumbstickFrame.AbsoluteSize.x/2,
7357				self.thumbstickFrame.AbsolutePosition.y + self.thumbstickFrame.AbsoluteSize.y/2)
7358			local direction = Vector2.new(inputObject.Position.x - centerPosition.x, inputObject.Position.y - centerPosition.y)
7359		end)
7360		
7361		self.onTouchMovedConn = UserInputService.TouchMoved:Connect(function(inputObject, isProcessed)
7362			if inputObject == self.moveTouchObject then
7363				centerPosition = Vector2.new(self.thumbstickFrame.AbsolutePosition.x + self.thumbstickFrame.AbsoluteSize.x/2,
7364					self.thumbstickFrame.AbsolutePosition.y + self.thumbstickFrame.AbsoluteSize.y/2)
7365				local direction = Vector2.new(inputObject.Position.x - centerPosition.x, inputObject.Position.y - centerPosition.y)
7366				DoMove(direction)
7367				MoveStick(inputObject.Position)
7368			end
7369		end)
7370		
7371		self.onTouchEndedConn = UserInputService.TouchEnded:Connect(function(inputObject, isProcessed)
7372			if inputObject == self.moveTouchObject then
7373				self:OnInputEnded()
7374			end
7375		end)
7376		
7377		GuiService.MenuOpened:Connect(function()
7378			if self.moveTouchObject then
7379				self:OnInputEnded()
7380			end
7381		end)	
7382		
7383		self.thumbstickFrame.Parent = parentFrame
7384	end
7385	return TouchThumbstick
7386end
7387
7388function _DynamicThumbstick()
7389	local ZERO_VECTOR3 = Vector3.new(0,0,0)
7390	local TOUCH_CONTROLS_SHEET = "rbxasset://textures/ui/Input/TouchControlsSheetV2.png"
7391	
7392	local DYNAMIC_THUMBSTICK_ACTION_NAME = "DynamicThumbstickAction"
7393	local DYNAMIC_THUMBSTICK_ACTION_PRIORITY = Enum.ContextActionPriority.High.Value
7394	
7395	local MIDDLE_TRANSPARENCIES = {
7396		1 - 0.89,
7397		1 - 0.70,
7398		1 - 0.60,
7399		1 - 0.50,
7400		1 - 0.40,
7401		1 - 0.30,
7402		1 - 0.25
7403	}
7404	local NUM_MIDDLE_IMAGES = #MIDDLE_TRANSPARENCIES
7405	
7406	local FADE_IN_OUT_BACKGROUND = true
7407	local FADE_IN_OUT_MAX_ALPHA = 0.35
7408	
7409	local FADE_IN_OUT_HALF_DURATION_DEFAULT = 0.3
7410	local FADE_IN_OUT_BALANCE_DEFAULT = 0.5
7411	local ThumbstickFadeTweenInfo = TweenInfo.new(0.15, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut)
7412	
7413	local Players = game:GetService("Players")
7414	local GuiService = game:GetService("GuiService")
7415	local UserInputService = game:GetService("UserInputService")
7416	local ContextActionService = game:GetService("ContextActionService")
7417	local RunService = game:GetService("RunService")
7418	local TweenService = game:GetService("TweenService")
7419	
7420	local LocalPlayer = Players.LocalPlayer
7421	if not LocalPlayer then
7422		Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
7423		LocalPlayer = Players.LocalPlayer
7424	end
7425	
7426	--[[ The Module ]]--
7427	local BaseCharacterController = _BaseCharacterController()
7428	local DynamicThumbstick = setmetatable({}, BaseCharacterController)
7429	DynamicThumbstick.__index = DynamicThumbstick
7430	
7431	function DynamicThumbstick.new()
7432		local self = setmetatable(BaseCharacterController.new(), DynamicThumbstick)
7433	
7434		self.moveTouchObject = nil
7435		self.moveTouchLockedIn = false
7436		self.moveTouchFirstChanged = false
7437		self.moveTouchStartPosition = nil
7438	
7439		self.startImage = nil
7440		self.endImage = nil
7441		self.middleImages = {}
7442	
7443		self.startImageFadeTween = nil
7444		self.endImageFadeTween = nil
7445		self.middleImageFadeTweens = {}
7446	
7447		self.isFirstTouch = true
7448	
7449		self.thumbstickFrame = nil
7450	
7451		self.onRenderSteppedConn = nil
7452	
7453		self.fadeInAndOutBalance = FADE_IN_OUT_BALANCE_DEFAULT
7454		self.fadeInAndOutHalfDuration = FADE_IN_OUT_HALF_DURATION_DEFAULT
7455		self.hasFadedBackgroundInPortrait = false
7456		self.hasFadedBackgroundInLandscape = false
7457	
7458		self.tweenInAlphaStart = nil
7459		self.tweenOutAlphaStart = nil
7460	
7461		return self
7462	end
7463	
7464	-- Note: Overrides base class GetIsJumping with get-and-clear behavior to do a single jump
7465	-- rather than sustained jumping. This is only to preserve the current behavior through the refactor.
7466	function DynamicThumbstick:GetIsJumping()
7467		local wasJumping = self.isJumping
7468		self.isJumping = false
7469		return wasJumping
7470	end
7471	
7472	function DynamicThumbstick:Enable(enable, uiParentFrame)
7473		if enable == nil then return false end			-- If nil, return false (invalid argument)
7474		enable = enable and true or false				-- Force anything non-nil to boolean before comparison
7475		if self.enabled == enable then return true end	-- If no state change, return true indicating already in requested state
7476	
7477		if enable then
7478			-- Enable
7479			if not self.thumbstickFrame then
7480				self:Create(uiParentFrame)
7481			end
7482	
7483			self:BindContextActions()
7484		else
7485			ContextActionService:UnbindAction(DYNAMIC_THUMBSTICK_ACTION_NAME)
7486			-- Disable
7487			self:OnInputEnded() -- Cleanup
7488		end
7489	
7490		self.enabled = enable
7491		self.thumbstickFrame.Visible = enable
7492	end
7493	
7494	-- Was called OnMoveTouchEnded in previous version
7495	function DynamicThumbstick:OnInputEnded()
7496		self.moveTouchObject = nil
7497		self.moveVector = ZERO_VECTOR3
7498		self:FadeThumbstick(false)
7499	end
7500	
7501	function DynamicThumbstick:FadeThumbstick(visible)
7502		if not visible and self.moveTouchObject then
7503			return
7504		end
7505		if self.isFirstTouch then return end
7506	
7507		if self.startImageFadeTween then
7508			self.startImageFadeTween:Cancel()
7509		end
7510		if self.endImageFadeTween then
7511			self.endImageFadeTween:Cancel()
7512		end
7513		for i = 1, #self.middleImages do
7514			if self.middleImageFadeTweens[i] then
7515				self.middleImageFadeTweens[i]:Cancel()
7516			end
7517		end
7518	
7519		if visible then
7520			self.startImageFadeTween = TweenService:Create(self.startImage, ThumbstickFadeTweenInfo, { ImageTransparency = 0 })
7521			self.startImageFadeTween:Play()
7522	
7523			self.endImageFadeTween = TweenService:Create(self.endImage, ThumbstickFadeTweenInfo, { ImageTransparency = 0.2 })
7524			self.endImageFadeTween:Play()
7525	
7526			for i = 1, #self.middleImages do
7527				self.middleImageFadeTweens[i] = TweenService:Create(self.middleImages[i], ThumbstickFadeTweenInfo, { ImageTransparency = MIDDLE_TRANSPARENCIES[i] })
7528				self.middleImageFadeTweens[i]:Play()
7529			end
7530		else
7531			self.startImageFadeTween = TweenService:Create(self.startImage, ThumbstickFadeTweenInfo, { ImageTransparency = 1 })
7532			self.startImageFadeTween:Play()
7533	
7534			self.endImageFadeTween = TweenService:Create(self.endImage, ThumbstickFadeTweenInfo, { ImageTransparency = 1 })
7535			self.endImageFadeTween:Play()
7536	
7537			for i = 1, #self.middleImages do
7538				self.middleImageFadeTweens[i] = TweenService:Create(self.middleImages[i], ThumbstickFadeTweenInfo, { ImageTransparency = 1 })
7539				self.middleImageFadeTweens[i]:Play()
7540			end
7541		end
7542	end
7543	
7544	function DynamicThumbstick:FadeThumbstickFrame(fadeDuration, fadeRatio)
7545		self.fadeInAndOutHalfDuration = fadeDuration * 0.5
7546		self.fadeInAndOutBalance = fadeRatio
7547		self.tweenInAlphaStart = tick()
7548	end
7549	
7550	function DynamicThumbstick:InputInFrame(inputObject)
7551		local frameCornerTopLeft = self.thumbstickFrame.AbsolutePosition
7552		local frameCornerBottomRight = frameCornerTopLeft + self.thumbstickFrame.AbsoluteSize
7553		local inputPosition = inputObject.Position
7554		if inputPosition.X >= frameCornerTopLeft.X and inputPosition.Y >= frameCornerTopLeft.Y then
7555			if inputPosition.X <= frameCornerBottomRight.X and inputPosition.Y <= frameCornerBottomRight.Y then
7556				return true
7557			end
7558		end
7559		return false
7560	end
7561	
7562	function DynamicThumbstick:DoFadeInBackground()
7563		local playerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
7564		local hasFadedBackgroundInOrientation = false
7565	
7566		-- only fade in/out the background once per orientation
7567		if playerGui then
7568			if playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeLeft or
7569				playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeRight then
7570					hasFadedBackgroundInOrientation = self.hasFadedBackgroundInLandscape
7571					self.hasFadedBackgroundInLandscape = true
7572			elseif playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.Portrait then
7573					hasFadedBackgroundInOrientation = self.hasFadedBackgroundInPortrait
7574					self.hasFadedBackgroundInPortrait = true
7575			end
7576		end
7577	
7578		if not hasFadedBackgroundInOrientation then
7579			self.fadeInAndOutHalfDuration = FADE_IN_OUT_HALF_DURATION_DEFAULT
7580			self.fadeInAndOutBalance = FADE_IN_OUT_BALANCE_DEFAULT
7581			self.tweenInAlphaStart = tick()
7582		end
7583	end
7584	
7585	function DynamicThumbstick:DoMove(direction)
7586		local currentMoveVector = direction
7587	
7588		-- Scaled Radial Dead Zone
7589		local inputAxisMagnitude = currentMoveVector.magnitude
7590		if inputAxisMagnitude < self.radiusOfDeadZone then
7591			currentMoveVector = ZERO_VECTOR3
7592		else
7593			currentMoveVector = currentMoveVector.unit*(
7594				1 - math.max(0, (self.radiusOfMaxSpeed - currentMoveVector.magnitude)/self.radiusOfMaxSpeed)
7595			)
7596			currentMoveVector = Vector3.new(currentMoveVector.x, 0, currentMoveVector.y)
7597		end
7598	
7599		self.moveVector = currentMoveVector
7600	end
7601	
7602	
7603	function DynamicThumbstick:LayoutMiddleImages(startPos, endPos)
7604		local startDist = (self.thumbstickSize / 2) + self.middleSize
7605		local vector = endPos - startPos
7606		local distAvailable = vector.magnitude - (self.thumbstickRingSize / 2) - self.middleSize
7607		local direction = vector.unit
7608	
7609		local distNeeded = self.middleSpacing * NUM_MIDDLE_IMAGES
7610		local spacing = self.middleSpacing
7611	
7612		if distNeeded < distAvailable then
7613			spacing = distAvailable / NUM_MIDDLE_IMAGES
7614		end
7615	
7616		for i = 1, NUM_MIDDLE_IMAGES do
7617			local image = self.middleImages[i]
7618			local distWithout = startDist + (spacing * (i - 2))
7619			local currentDist = startDist + (spacing * (i - 1))
7620	
7621			if distWithout < distAvailable then
7622				local pos = endPos - direction * currentDist
7623				local exposedFraction = math.clamp(1 - ((currentDist - distAvailable) / spacing), 0, 1)
7624	
7625				image.Visible = true
7626				image.Position = UDim2.new(0, pos.X, 0, pos.Y)
7627				image.Size = UDim2.new(0, self.middleSize * exposedFraction, 0, self.middleSize * exposedFraction)
7628			else
7629				image.Visible = false
7630			end
7631		end
7632	end
7633	
7634	function DynamicThumbstick:MoveStick(pos)
7635		local vector2StartPosition = Vector2.new(self.moveTouchStartPosition.X, self.moveTouchStartPosition.Y)
7636		local startPos = vector2StartPosition - self.thumbstickFrame.AbsolutePosition
7637		local endPos = Vector2.new(pos.X, pos.Y) - self.thumbstickFrame.AbsolutePosition
7638		self.endImage.Position = UDim2.new(0, endPos.X, 0, endPos.Y)
7639		self:LayoutMiddleImages(startPos, endPos)
7640	end
7641	
7642	function DynamicThumbstick:BindContextActions()
7643		local function inputBegan(inputObject)
7644			if self.moveTouchObject then
7645				return Enum.ContextActionResult.Pass
7646			end
7647	
7648			if not self:InputInFrame(inputObject) then
7649				return Enum.ContextActionResult.Pass
7650			end
7651	
7652			if self.isFirstTouch then
7653				self.isFirstTouch = false
7654				local tweenInfo = TweenInfo.new(0.5, Enum.EasingStyle.Quad, Enum.EasingDirection.Out,0,false,0)
7655				TweenService:Create(self.startImage, tweenInfo, {Size = UDim2.new(0, 0, 0, 0)}):Play()
7656				TweenService:Create(
7657					self.endImage,
7658					tweenInfo,
7659					{Size = UDim2.new(0, self.thumbstickSize, 0, self.thumbstickSize), ImageColor3 = Color3.new(0,0,0)}
7660				):Play()
7661			end
7662	
7663			self.moveTouchLockedIn = false
7664			self.moveTouchObject = inputObject
7665			self.moveTouchStartPosition = inputObject.Position
7666			self.moveTouchFirstChanged = true
7667	
7668			if FADE_IN_OUT_BACKGROUND then
7669				self:DoFadeInBackground()
7670			end
7671	
7672			return Enum.ContextActionResult.Pass
7673		end
7674	
7675		local function inputChanged(inputObject)
7676			if inputObject == self.moveTouchObject then
7677				if self.moveTouchFirstChanged then
7678					self.moveTouchFirstChanged = false
7679	
7680					local startPosVec2 = Vector2.new(
7681						inputObject.Position.X - self.thumbstickFrame.AbsolutePosition.X,
7682						inputObject.Position.Y - self.thumbstickFrame.AbsolutePosition.Y
7683					)
7684					self.startImage.Visible = true
7685					self.startImage.Position = UDim2.new(0, startPosVec2.X, 0, startPosVec2.Y)
7686					self.endImage.Visible = true
7687					self.endImage.Position = self.startImage.Position
7688	
7689					self:FadeThumbstick(true)
7690					self:MoveStick(inputObject.Position)
7691				end
7692	
7693				self.moveTouchLockedIn = true
7694	
7695				local direction = Vector2.new(
7696					inputObject.Position.x - self.moveTouchStartPosition.x,
7697					inputObject.Position.y - self.moveTouchStartPosition.y
7698				)
7699				if math.abs(direction.x) > 0 or math.abs(direction.y) > 0 then
7700					self:DoMove(direction)
7701					self:MoveStick(inputObject.Position)
7702				end
7703				return Enum.ContextActionResult.Sink
7704			end
7705			return Enum.ContextActionResult.Pass
7706		end
7707	
7708		local function inputEnded(inputObject)
7709			if inputObject == self.moveTouchObject then
7710				self:OnInputEnded()
7711				if self.moveTouchLockedIn then
7712					return Enum.ContextActionResult.Sink
7713				end
7714			end
7715			return Enum.ContextActionResult.Pass
7716		end
7717	
7718		local function handleInput(actionName, inputState, inputObject)
7719			if inputState == Enum.UserInputState.Begin then
7720				return inputBegan(inputObject)
7721			elseif inputState == Enum.UserInputState.Change then
7722				return inputChanged(inputObject)
7723			elseif inputState == Enum.UserInputState.End then
7724				return inputEnded(inputObject)
7725			elseif inputState == Enum.UserInputState.Cancel then
7726				self:OnInputEnded()
7727			end
7728		end
7729	
7730		ContextActionService:BindActionAtPriority(
7731			DYNAMIC_THUMBSTICK_ACTION_NAME,
7732			handleInput,
7733			false,
7734			DYNAMIC_THUMBSTICK_ACTION_PRIORITY,
7735			Enum.UserInputType.Touch)
7736	end
7737	
7738	function DynamicThumbstick:Create(parentFrame)
7739		if self.thumbstickFrame then
7740			self.thumbstickFrame:Destroy()
7741			self.thumbstickFrame = nil
7742			if self.onRenderSteppedConn then
7743				self.onRenderSteppedConn:Disconnect()
7744				self.onRenderSteppedConn = nil
7745			end
7746		end
7747	
7748		self.thumbstickSize = 45
7749		self.thumbstickRingSize = 20
7750		self.middleSize = 10
7751		self.middleSpacing = self.middleSize + 4
7752		self.radiusOfDeadZone = 2
7753		self.radiusOfMaxSpeed = 20
7754	
7755		local screenSize = parentFrame.AbsoluteSize
7756		local isBigScreen = math.min(screenSize.x, screenSize.y) > 500
7757		if isBigScreen then
7758			self.thumbstickSize = self.thumbstickSize * 2
7759			self.thumbstickRingSize = self.thumbstickRingSize * 2
7760			self.middleSize = self.middleSize * 2
7761			self.middleSpacing = self.middleSpacing * 2
7762			self.radiusOfDeadZone = self.radiusOfDeadZone * 2
7763			self.radiusOfMaxSpeed = self.radiusOfMaxSpeed * 2
7764		end
7765	
7766		local function layoutThumbstickFrame(portraitMode)
7767			if portraitMode then
7768				self.thumbstickFrame.Size = UDim2.new(1, 0, 0.4, 0)
7769				self.thumbstickFrame.Position = UDim2.new(0, 0, 0.6, 0)
7770			else
7771				self.thumbstickFrame.Size = UDim2.new(0.4, 0, 2/3, 0)
7772				self.thumbstickFrame.Position = UDim2.new(0, 0, 1/3, 0)
7773			end
7774		end
7775	
7776		self.thumbstickFrame = Instance.new("Frame")
7777		self.thumbstickFrame.BorderSizePixel = 0
7778		self.thumbstickFrame.Name = "DynamicThumbstickFrame"
7779		self.thumbstickFrame.Visible = false
7780		self.thumbstickFrame.BackgroundTransparency = 1.0
7781		self.thumbstickFrame.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
7782		self.thumbstickFrame.Active = false
7783		layoutThumbstickFrame(false)
7784	
7785		self.startImage = Instance.new("ImageLabel")
7786		self.startImage.Name = "ThumbstickStart"
7787		self.startImage.Visible = true
7788		self.startImage.BackgroundTransparency = 1
7789		self.startImage.Image = TOUCH_CONTROLS_SHEET
7790		self.startImage.ImageRectOffset = Vector2.new(1,1)
7791		self.startImage.ImageRectSize = Vector2.new(144, 144)
7792		self.startImage.ImageColor3 = Color3.new(0, 0, 0)
7793		self.startImage.AnchorPoint = Vector2.new(0.5, 0.5)
7794		self.startImage.Position = UDim2.new(0, self.thumbstickRingSize * 3.3, 1, -self.thumbstickRingSize  * 2.8)
7795		self.startImage.Size = UDim2.new(0, self.thumbstickRingSize  * 3.7, 0, self.thumbstickRingSize  * 3.7)
7796		self.startImage.ZIndex = 10
7797		self.startImage.Parent = self.thumbstickFrame
7798	
7799		self.endImage = Instance.new("ImageLabel")
7800		self.endImage.Name = "ThumbstickEnd"
7801		self.endImage.Visible = true
7802		self.endImage.BackgroundTransparency = 1
7803		self.endImage.Image = TOUCH_CONTROLS_SHEET
7804		self.endImage.ImageRectOffset = Vector2.new(1,1)
7805		self.endImage.ImageRectSize =  Vector2.new(144, 144)
7806		self.endImage.AnchorPoint = Vector2.new(0.5, 0.5)
7807		self.endImage.Position = self.startImage.Position
7808		self.endImage.Size = UDim2.new(0, self.thumbstickSize * 0.8, 0, self.thumbstickSize * 0.8)
7809		self.endImage.ZIndex = 10
7810		self.endImage.Parent = self.thumbstickFrame
7811	
7812		for i = 1, NUM_MIDDLE_IMAGES do
7813			self.middleImages[i] = Instance.new("ImageLabel")
7814			self.middleImages[i].Name = "ThumbstickMiddle"
7815			self.middleImages[i].Visible = false
7816			self.middleImages[i].BackgroundTransparency = 1
7817			self.middleImages[i].Image = TOUCH_CONTROLS_SHEET
7818			self.middleImages[i].ImageRectOffset = Vector2.new(1,1)
7819			self.middleImages[i].ImageRectSize = Vector2.new(144, 144)
7820			self.middleImages[i].ImageTransparency = MIDDLE_TRANSPARENCIES[i]
7821			self.middleImages[i].AnchorPoint = Vector2.new(0.5, 0.5)
7822			self.middleImages[i].ZIndex = 9
7823			self.middleImages[i].Parent = self.thumbstickFrame
7824		end
7825	
7826		local CameraChangedConn = nil
7827		local function onCurrentCameraChanged()
7828			if CameraChangedConn then
7829				CameraChangedConn:Disconnect()
7830				CameraChangedConn = nil
7831			end
7832			local newCamera = workspace.CurrentCamera
7833			if newCamera then
7834				local function onViewportSizeChanged()
7835					local size = newCamera.ViewportSize
7836					local portraitMode = size.X < size.Y
7837					layoutThumbstickFrame(portraitMode)
7838				end
7839				CameraChangedConn = newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(onViewportSizeChanged)
7840				onViewportSizeChanged()
7841			end
7842		end
7843		workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(onCurrentCameraChanged)
7844		if workspace.CurrentCamera then
7845			onCurrentCameraChanged()
7846		end
7847	
7848		self.moveTouchStartPosition = nil
7849	
7850		self.startImageFadeTween = nil
7851		self.endImageFadeTween = nil
7852		self.middleImageFadeTweens = {}
7853	
7854		self.onRenderSteppedConn = RunService.RenderStepped:Connect(function()
7855			if self.tweenInAlphaStart ~= nil then
7856				local delta = tick() - self.tweenInAlphaStart
7857				local fadeInTime = (self.fadeInAndOutHalfDuration * 2 * self.fadeInAndOutBalance)
7858				self.thumbstickFrame.BackgroundTransparency = 1 - FADE_IN_OUT_MAX_ALPHA*math.min(delta/fadeInTime, 1)
7859				if delta > fadeInTime then
7860					self.tweenOutAlphaStart = tick()
7861					self.tweenInAlphaStart = nil
7862				end
7863			elseif self.tweenOutAlphaStart ~= nil then
7864				local delta = tick() - self.tweenOutAlphaStart
7865				local fadeOutTime = (self.fadeInAndOutHalfDuration * 2) - (self.fadeInAndOutHalfDuration * 2 * self.fadeInAndOutBalance)
7866				self.thumbstickFrame.BackgroundTransparency = 1 - FADE_IN_OUT_MAX_ALPHA + FADE_IN_OUT_MAX_ALPHA*math.min(delta/fadeOutTime, 1)
7867				if delta > fadeOutTime  then
7868					self.tweenOutAlphaStart = nil
7869				end
7870			end
7871		end)
7872	
7873		self.onTouchEndedConn = UserInputService.TouchEnded:connect(function(inputObject)
7874			if inputObject == self.moveTouchObject then
7875				self:OnInputEnded()
7876			end
7877		end)
7878	
7879		GuiService.MenuOpened:connect(function()
7880			if self.moveTouchObject then
7881				self:OnInputEnded()
7882			end
7883		end)
7884	
7885		local playerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
7886		while not playerGui do
7887			LocalPlayer.ChildAdded:wait()
7888			playerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
7889		end
7890	
7891		local playerGuiChangedConn = nil
7892		local originalScreenOrientationWasLandscape =	playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeLeft or
7893														playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeRight
7894	
7895		local function longShowBackground()
7896			self.fadeInAndOutHalfDuration = 2.5
7897			self.fadeInAndOutBalance = 0.05
7898			self.tweenInAlphaStart = tick()
7899		end
7900	
7901		playerGuiChangedConn = playerGui:GetPropertyChangedSignal("CurrentScreenOrientation"):Connect(function()
7902			if (originalScreenOrientationWasLandscape and playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.Portrait) or
7903				(not originalScreenOrientationWasLandscape and playerGui.CurrentScreenOrientation ~= Enum.ScreenOrientation.Portrait) then
7904	
7905				playerGuiChangedConn:disconnect()
7906				longShowBackground()
7907	
7908				if originalScreenOrientationWasLandscape then
7909					self.hasFadedBackgroundInPortrait = true
7910				else
7911					self.hasFadedBackgroundInLandscape = true
7912				end
7913			end
7914		end)
7915	
7916		self.thumbstickFrame.Parent = parentFrame
7917	
7918		if game:IsLoaded() then
7919			longShowBackground()
7920		else
7921			coroutine.wrap(function()
7922				game.Loaded:Wait()
7923				longShowBackground()
7924			end)()
7925		end
7926	end
7927	
7928	return DynamicThumbstick
7929end
7930
7931function _Gamepad()
7932	local UserInputService = game:GetService("UserInputService")
7933	local ContextActionService = game:GetService("ContextActionService")
7934	
7935	--[[ Constants ]]--
7936	local ZERO_VECTOR3 = Vector3.new(0,0,0)
7937	local NONE = Enum.UserInputType.None
7938	local thumbstickDeadzone = 0.2
7939	
7940	--[[ The Module ]]--
7941	local BaseCharacterController = _BaseCharacterController()
7942	local Gamepad = setmetatable({}, BaseCharacterController)
7943	Gamepad.__index = Gamepad
7944	
7945	function Gamepad.new(CONTROL_ACTION_PRIORITY)
7946		local self = setmetatable(BaseCharacterController.new(), Gamepad)
7947	
7948		self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY
7949	
7950		self.forwardValue  = 0
7951		self.backwardValue = 0
7952		self.leftValue = 0
7953		self.rightValue = 0
7954	
7955		self.activeGamepad = NONE	-- Enum.UserInputType.Gamepad1, 2, 3...
7956		self.gamepadConnectedConn = nil
7957		self.gamepadDisconnectedConn = nil
7958		return self
7959	end
7960	
7961	function Gamepad:Enable(enable)
7962		if not UserInputService.GamepadEnabled then
7963			return false
7964		end
7965	
7966		if enable == self.enabled then
7967			-- Module is already in the state being requested. True is returned here since the module will be in the state
7968			-- expected by the code that follows the Enable() call. This makes more sense than returning false to indicate
7969			-- no action was necessary. False indicates failure to be in requested/expected state.
7970			return true
7971		end
7972	
7973		self.forwardValue  = 0
7974		self.backwardValue = 0
7975		self.leftValue = 0
7976		self.rightValue = 0
7977		self.moveVector = ZERO_VECTOR3
7978		self.isJumping = false
7979	
7980		if enable then
7981			self.activeGamepad = self:GetHighestPriorityGamepad()
7982			if self.activeGamepad ~= NONE then
7983				self:BindContextActions()
7984				self:ConnectGamepadConnectionListeners()
7985			else
7986				-- No connected gamepads, failure to enable
7987				return false
7988			end
7989		else
7990			self:UnbindContextActions()
7991			self:DisconnectGamepadConnectionListeners()
7992			self.activeGamepad = NONE
7993		end
7994	
7995		self.enabled = enable
7996		return true
7997	end
7998	
7999	-- This function selects the lowest number gamepad from the currently-connected gamepad
8000	-- and sets it as the active gamepad
8001	function Gamepad:GetHighestPriorityGamepad()
8002		local connectedGamepads = UserInputService:GetConnectedGamepads()
8003		local bestGamepad = NONE -- Note that this value is higher than all valid gamepad values
8004		for _, gamepad in pairs(connectedGamepads) do
8005			if gamepad.Value < bestGamepad.Value then
8006				bestGamepad = gamepad
8007			end
8008		end
8009		return bestGamepad
8010	end
8011	
8012	function Gamepad:BindContextActions()
8013	
8014		if self.activeGamepad == NONE then
8015			-- There must be an active gamepad to set up bindings
8016			return false
8017		end
8018	
8019		local handleJumpAction = function(actionName, inputState, inputObject)
8020			self.isJumping = (inputState == Enum.UserInputState.Begin)
8021			return Enum.ContextActionResult.Sink
8022		end
8023	
8024		local handleThumbstickInput = function(actionName, inputState, inputObject)
8025	
8026			if inputState == Enum.UserInputState.Cancel then
8027				self.moveVector = ZERO_VECTOR3
8028				return Enum.ContextActionResult.Sink
8029			end
8030	
8031			if self.activeGamepad ~= inputObject.UserInputType then
8032				return Enum.ContextActionResult.Pass
8033			end
8034			if inputObject.KeyCode ~= Enum.KeyCode.Thumbstick1 then return end
8035	
8036			if inputObject.Position.magnitude > thumbstickDeadzone then
8037				self.moveVector  =  Vector3.new(inputObject.Position.X, 0, -inputObject.Position.Y)
8038			else
8039				self.moveVector = ZERO_VECTOR3
8040			end
8041			return Enum.ContextActionResult.Sink
8042		end
8043	
8044		ContextActionService:BindActivate(self.activeGamepad, Enum.KeyCode.ButtonR2)
8045		ContextActionService:BindActionAtPriority("jumpAction", handleJumpAction, false,
8046			self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.ButtonA)
8047		ContextActionService:BindActionAtPriority("moveThumbstick", handleThumbstickInput, false,
8048			self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.Thumbstick1)
8049	
8050		return true
8051	end
8052	
8053	function Gamepad:UnbindContextActions()
8054		if self.activeGamepad ~= NONE then
8055			ContextActionService:UnbindActivate(self.activeGamepad, Enum.KeyCode.ButtonR2)
8056		end
8057		ContextActionService:UnbindAction("moveThumbstick")
8058		ContextActionService:UnbindAction("jumpAction")
8059	end
8060	
8061	function Gamepad:OnNewGamepadConnected()
8062		-- A new gamepad has been connected.
8063		local bestGamepad = self:GetHighestPriorityGamepad()
8064	
8065		if bestGamepad == self.activeGamepad then
8066			-- A new gamepad was connected, but our active gamepad is not changing
8067			return
8068		end
8069	
8070		if bestGamepad == NONE then
8071			-- There should be an active gamepad when GamepadConnected fires, so this should not
8072			-- normally be hit. If there is no active gamepad, unbind actions but leave
8073			-- the module enabled and continue to listen for a new gamepad connection.
8074			warn("Gamepad:OnNewGamepadConnected found no connected gamepads")
8075			self:UnbindContextActions()
8076			return
8077		end
8078	
8079		if self.activeGamepad ~= NONE then
8080			-- Switching from one active gamepad to another
8081			self:UnbindContextActions()
8082		end
8083	
8084		self.activeGamepad = bestGamepad
8085		self:BindContextActions()
8086	end
8087	
8088	function Gamepad:OnCurrentGamepadDisconnected()
8089		if self.activeGamepad ~= NONE then
8090			ContextActionService:UnbindActivate(self.activeGamepad, Enum.KeyCode.ButtonR2)
8091		end
8092	
8093		local bestGamepad = self:GetHighestPriorityGamepad()
8094	
8095		if self.activeGamepad ~= NONE and bestGamepad == self.activeGamepad then
8096			warn("Gamepad:OnCurrentGamepadDisconnected found the supposedly disconnected gamepad in connectedGamepads.")
8097			self:UnbindContextActions()
8098			self.activeGamepad = NONE
8099			return
8100		end
8101	
8102		if bestGamepad == NONE then
8103			-- No active gamepad, unbinding actions but leaving gamepad connection listener active
8104			self:UnbindContextActions()
8105			self.activeGamepad = NONE
8106		else
8107			-- Set new gamepad as active and bind to tool activation
8108			self.activeGamepad = bestGamepad
8109			ContextActionService:BindActivate(self.activeGamepad, Enum.KeyCode.ButtonR2)
8110		end
8111	end
8112	
8113	function Gamepad:ConnectGamepadConnectionListeners()
8114		self.gamepadConnectedConn = UserInputService.GamepadConnected:Connect(function(gamepadEnum)
8115			self:OnNewGamepadConnected()
8116		end)
8117	
8118		self.gamepadDisconnectedConn = UserInputService.GamepadDisconnected:Connect(function(gamepadEnum)
8119			if self.activeGamepad == gamepadEnum then
8120				self:OnCurrentGamepadDisconnected()
8121			end
8122		end)
8123	
8124	end
8125	
8126	function Gamepad:DisconnectGamepadConnectionListeners()
8127		if self.gamepadConnectedConn then
8128			self.gamepadConnectedConn:Disconnect()
8129			self.gamepadConnectedConn = nil
8130		end
8131	
8132		if self.gamepadDisconnectedConn then
8133			self.gamepadDisconnectedConn:Disconnect()
8134			self.gamepadDisconnectedConn = nil
8135		end
8136	end
8137	
8138	return Gamepad
8139end
8140
8141function _Keyboard()
8142	
8143	--[[ Roblox Services ]]--
8144	local UserInputService = game:GetService("UserInputService")
8145	local ContextActionService = game:GetService("ContextActionService")
8146	
8147	--[[ Constants ]]--
8148	local ZERO_VECTOR3 = Vector3.new(0,0,0)
8149	
8150	--[[ The Module ]]--
8151	local BaseCharacterController = _BaseCharacterController()
8152	local Keyboard = setmetatable({}, BaseCharacterController)
8153	Keyboard.__index = Keyboard
8154	
8155	function Keyboard.new(CONTROL_ACTION_PRIORITY)
8156		local self = setmetatable(BaseCharacterController.new(), Keyboard)
8157	
8158		self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY
8159	
8160		self.textFocusReleasedConn = nil
8161		self.textFocusGainedConn = nil
8162		self.windowFocusReleasedConn = nil
8163	
8164		self.forwardValue  = 0
8165		self.backwardValue = 0
8166		self.leftValue = 0
8167		self.rightValue = 0
8168	
8169		self.jumpEnabled = true
8170	
8171		return self
8172	end
8173	
8174	function Keyboard:Enable(enable)
8175		if not UserInputService.KeyboardEnabled then
8176			return false
8177		end
8178	
8179		if enable == self.enabled then
8180			-- Module is already in the state being requested. True is returned here since the module will be in the state
8181			-- expected by the code that follows the Enable() call. This makes more sense than returning false to indicate
8182			-- no action was necessary. False indicates failure to be in requested/expected state.
8183			return true
8184		end
8185	
8186		self.forwardValue  = 0
8187		self.backwardValue = 0
8188		self.leftValue = 0
8189		self.rightValue = 0
8190		self.moveVector = ZERO_VECTOR3
8191		self.jumpRequested = false
8192		self:UpdateJump()
8193	
8194		if enable then
8195			self:BindContextActions()
8196			self:ConnectFocusEventListeners()
8197		else
8198			self:UnbindContextActions()
8199			self:DisconnectFocusEventListeners()
8200		end
8201	
8202		self.enabled = enable
8203		return true
8204	end
8205	
8206	function Keyboard:UpdateMovement(inputState)
8207		if inputState == Enum.UserInputState.Cancel then
8208			self.moveVector = ZERO_VECTOR3
8209		else
8210			self.moveVector = Vector3.new(self.leftValue + self.rightValue, 0, self.forwardValue + self.backwardValue)
8211		end
8212	end
8213	
8214	function Keyboard:UpdateJump()
8215		self.isJumping = self.jumpRequested
8216	end
8217	
8218	function Keyboard:BindContextActions()
8219	
8220		-- Note: In the previous version of this code, the movement values were not zeroed-out on UserInputState. Cancel, now they are,
8221		-- which fixes them from getting stuck on.
8222		-- We return ContextActionResult.Pass here for legacy reasons.
8223		-- Many games rely on gameProcessedEvent being false on UserInputService.InputBegan for these control actions.
8224		local handleMoveForward = function(actionName, inputState, inputObject)
8225			self.forwardValue = (inputState == Enum.UserInputState.Begin) and -1 or 0
8226			self:UpdateMovement(inputState)
8227			return Enum.ContextActionResult.Pass
8228		end
8229	
8230		local handleMoveBackward = function(actionName, inputState, inputObject)
8231			self.backwardValue = (inputState == Enum.UserInputState.Begin) and 1 or 0
8232			self:UpdateMovement(inputState)
8233			return Enum.ContextActionResult.Pass
8234		end
8235	
8236		local handleMoveLeft = function(actionName, inputState, inputObject)
8237			self.leftValue = (inputState == Enum.UserInputState.Begin) and -1 or 0
8238			self:UpdateMovement(inputState)
8239			return Enum.ContextActionResult.Pass
8240		end
8241	
8242		local handleMoveRight = function(actionName, inputState, inputObject)
8243			self.rightValue = (inputState == Enum.UserInputState.Begin) and 1 or 0
8244			self:UpdateMovement(inputState)
8245			return Enum.ContextActionResult.Pass
8246		end
8247	
8248		local handleJumpAction = function(actionName, inputState, inputObject)
8249			self.jumpRequested = self.jumpEnabled and (inputState == Enum.UserInputState.Begin)
8250			self:UpdateJump()
8251			return Enum.ContextActionResult.Pass
8252		end
8253	
8254		-- TODO: Revert to KeyCode bindings so that in the future the abstraction layer from actual keys to
8255		-- movement direction is done in Lua
8256		ContextActionService:BindActionAtPriority("moveForwardAction", handleMoveForward, false,
8257			self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterForward)
8258		ContextActionService:BindActionAtPriority("moveBackwardAction", handleMoveBackward, false,
8259			self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterBackward)
8260		ContextActionService:BindActionAtPriority("moveLeftAction", handleMoveLeft, false,
8261			self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterLeft)
8262		ContextActionService:BindActionAtPriority("moveRightAction", handleMoveRight, false,
8263			self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterRight)
8264		ContextActionService:BindActionAtPriority("jumpAction", handleJumpAction, false,
8265			self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterJump)
8266	end
8267	
8268	function Keyboard:UnbindContextActions()
8269		ContextActionService:UnbindAction("moveForwardAction")
8270		ContextActionService:UnbindAction("moveBackwardAction")
8271		ContextActionService:UnbindAction("moveLeftAction")
8272		ContextActionService:UnbindAction("moveRightAction")
8273		ContextActionService:UnbindAction("jumpAction")
8274	end
8275	
8276	function Keyboard:ConnectFocusEventListeners()
8277		local function onFocusReleased()
8278			self.moveVector = ZERO_VECTOR3
8279			self.forwardValue  = 0
8280			self.backwardValue = 0
8281			self.leftValue = 0
8282			self.rightValue = 0
8283			self.jumpRequested = false
8284			self:UpdateJump()
8285		end
8286	
8287		local function onTextFocusGained(textboxFocused)
8288			self.jumpRequested = false
8289			self:UpdateJump()
8290		end
8291	
8292		self.textFocusReleasedConn = UserInputService.TextBoxFocusReleased:Connect(onFocusReleased)
8293		self.textFocusGainedConn = UserInputService.TextBoxFocused:Connect(onTextFocusGained)
8294		self.windowFocusReleasedConn = UserInputService.WindowFocused:Connect(onFocusReleased)
8295	end
8296	
8297	function Keyboard:DisconnectFocusEventListeners()
8298		if self.textFocusReleasedCon then
8299			self.textFocusReleasedCon:Disconnect()
8300			self.textFocusReleasedCon = nil
8301		end
8302		if self.textFocusGainedConn then
8303			self.textFocusGainedConn:Disconnect()
8304			self.textFocusGainedConn = nil
8305		end
8306		if self.windowFocusReleasedConn then
8307			self.windowFocusReleasedConn:Disconnect()
8308			self.windowFocusReleasedConn = nil
8309		end
8310	end
8311	
8312	return Keyboard
8313end
8314
8315function _ControlModule()
8316	local ControlModule = {}
8317	ControlModule.__index = ControlModule
8318	
8319	--[[ Roblox Services ]]--
8320	local Players = game:GetService("Players")
8321	local RunService = game:GetService("RunService")
8322	local UserInputService = game:GetService("UserInputService")
8323	local Workspace = game:GetService("Workspace")
8324	local UserGameSettings = UserSettings():GetService("UserGameSettings")
8325	
8326	-- Roblox User Input Control Modules - each returns a new() constructor function used to create controllers as needed
8327	local Keyboard = _Keyboard()
8328	local Gamepad = _Gamepad()
8329	local DynamicThumbstick = _DynamicThumbstick()
8330	
8331	local FFlagUserMakeThumbstickDynamic do
8332		local success, value = pcall(function()
8333			return UserSettings():IsUserFeatureEnabled("UserMakeThumbstickDynamic")
8334		end)
8335		FFlagUserMakeThumbstickDynamic = success and value
8336	end
8337	
8338	local TouchThumbstick = FFlagUserMakeThumbstickDynamic and DynamicThumbstick or _TouchThumbstick()
8339	
8340	-- These controllers handle only walk/run movement, jumping is handled by the
8341	-- TouchJump controller if any of these are active
8342	local ClickToMove = _ClickToMoveController()
8343	local TouchJump = _TouchJump()
8344	
8345	local VehicleController = _VehicleController()
8346	
8347	local CONTROL_ACTION_PRIORITY = Enum.ContextActionPriority.Default.Value
8348	
8349	-- Mapping from movement mode and lastInputType enum values to control modules to avoid huge if elseif switching
8350	local movementEnumToModuleMap = {
8351		[Enum.TouchMovementMode.DPad] = DynamicThumbstick,
8352		[Enum.DevTouchMovementMode.DPad] = DynamicThumbstick,
8353		[Enum.TouchMovementMode.Thumbpad] = DynamicThumbstick,
8354		[Enum.DevTouchMovementMode.Thumbpad] = DynamicThumbstick,
8355		[Enum.TouchMovementMode.Thumbstick] = TouchThumbstick,
8356		[Enum.DevTouchMovementMode.Thumbstick] = TouchThumbstick,
8357		[Enum.TouchMovementMode.DynamicThumbstick] = DynamicThumbstick,
8358		[Enum.DevTouchMovementMode.DynamicThumbstick] = DynamicThumbstick,
8359		[Enum.TouchMovementMode.ClickToMove] = ClickToMove,
8360		[Enum.DevTouchMovementMode.ClickToMove] = ClickToMove,
8361	
8362		-- Current default
8363		[Enum.TouchMovementMode.Default] = DynamicThumbstick,
8364	
8365		[Enum.ComputerMovementMode.Default] = Keyboard,
8366		[Enum.ComputerMovementMode.KeyboardMouse] = Keyboard,
8367		[Enum.DevComputerMovementMode.KeyboardMouse] = Keyboard,
8368		[Enum.DevComputerMovementMode.Scriptable] = nil,
8369		[Enum.ComputerMovementMode.ClickToMove] = ClickToMove,
8370		[Enum.DevComputerMovementMode.ClickToMove] = ClickToMove,
8371	}
8372	
8373	-- Keyboard controller is really keyboard and mouse controller
8374	local computerInputTypeToModuleMap = {
8375		[Enum.UserInputType.Keyboard] = Keyboard,
8376		[Enum.UserInputType.MouseButton1] = Keyboard,
8377		[Enum.UserInputType.MouseButton2] = Keyboard,
8378		[Enum.UserInputType.MouseButton3] = Keyboard,
8379		[Enum.UserInputType.MouseWheel] = Keyboard,
8380		[Enum.UserInputType.MouseMovement] = Keyboard,
8381		[Enum.UserInputType.Gamepad1] = Gamepad,
8382		[Enum.UserInputType.Gamepad2] = Gamepad,
8383		[Enum.UserInputType.Gamepad3] = Gamepad,
8384		[Enum.UserInputType.Gamepad4] = Gamepad,
8385	}
8386	
8387	local lastInputType
8388	
8389	function ControlModule.new()
8390		local self = setmetatable({},ControlModule)
8391	
8392		-- The Modules above are used to construct controller instances as-needed, and this
8393		-- table is a map from Module to the instance created from it
8394		self.controllers = {}
8395	
8396		self.activeControlModule = nil	-- Used to prevent unnecessarily expensive checks on each input event
8397		self.activeController = nil
8398		self.touchJumpController = nil
8399		self.moveFunction = Players.LocalPlayer.Move
8400		self.humanoid = nil
8401		self.lastInputType = Enum.UserInputType.None
8402	
8403		-- For Roblox self.vehicleController
8404		self.humanoidSeatedConn = nil
8405		self.vehicleController = nil
8406	
8407		self.touchControlFrame = nil
8408	
8409		self.vehicleController = VehicleController.new(CONTROL_ACTION_PRIORITY)
8410	
8411		Players.LocalPlayer.CharacterAdded:Connect(function(char) self:OnCharacterAdded(char) end)
8412		Players.LocalPlayer.CharacterRemoving:Connect(function(char) self:OnCharacterRemoving(char) end)
8413		if Players.LocalPlayer.Character then
8414			self:OnCharacterAdded(Players.LocalPlayer.Character)
8415		end
8416	
8417		RunService:BindToRenderStep("ControlScriptRenderstep", Enum.RenderPriority.Input.Value, function(dt)
8418			self:OnRenderStepped(dt)
8419		end)
8420	
8421		UserInputService.LastInputTypeChanged:Connect(function(newLastInputType)
8422			self:OnLastInputTypeChanged(newLastInputType)
8423		end)
8424	
8425	
8426		UserGameSettings:GetPropertyChangedSignal("TouchMovementMode"):Connect(function()
8427			self:OnTouchMovementModeChange()
8428		end)
8429		Players.LocalPlayer:GetPropertyChangedSignal("DevTouchMovementMode"):Connect(function()
8430			self:OnTouchMovementModeChange()
8431		end)
8432	
8433		UserGameSettings:GetPropertyChangedSignal("ComputerMovementMode"):Connect(function()
8434			self:OnComputerMovementModeChange()
8435		end)
8436		Players.LocalPlayer:GetPropertyChangedSignal("DevComputerMovementMode"):Connect(function()
8437			self:OnComputerMovementModeChange()
8438		end)
8439	
8440		--[[ Touch Device UI ]]--
8441		self.playerGui = nil
8442		self.touchGui = nil
8443		self.playerGuiAddedConn = nil
8444	
8445		if UserInputService.TouchEnabled then
8446			self.playerGui = Players.LocalPlayer:FindFirstChildOfClass("PlayerGui")
8447			if self.playerGui then
8448				self:CreateTouchGuiContainer()
8449				self:OnLastInputTypeChanged(UserInputService:GetLastInputType())
8450			else
8451				self.playerGuiAddedConn = Players.LocalPlayer.ChildAdded:Connect(function(child)
8452					if child:IsA("PlayerGui") then
8453						self.playerGui = child
8454						self:CreateTouchGuiContainer()
8455						self.playerGuiAddedConn:Disconnect()
8456						self.playerGuiAddedConn = nil
8457						self:OnLastInputTypeChanged(UserInputService:GetLastInputType())
8458					end
8459				end)
8460			end
8461		else
8462			self:OnLastInputTypeChanged(UserInputService:GetLastInputType())
8463		end
8464	
8465		return self
8466	end
8467	
8468	-- Convenience function so that calling code does not have to first get the activeController
8469	-- and then call GetMoveVector on it. When there is no active controller, this function returns
8470	-- nil so that this case can be distinguished from no current movement (which returns zero vector).
8471	function ControlModule:GetMoveVector()
8472		if self.activeController then
8473			return self.activeController:GetMoveVector()
8474		end
8475		return Vector3.new(0,0,0)
8476	end
8477	
8478	function ControlModule:GetActiveController()
8479		return self.activeController
8480	end
8481	
8482	function ControlModule:EnableActiveControlModule()
8483		if self.activeControlModule == ClickToMove then
8484			-- For ClickToMove, when it is the player's choice, we also enable the full keyboard controls.
8485			-- When the developer is forcing click to move, the most keyboard controls (WASD) are not available, only jump.
8486			self.activeController:Enable(
8487				true,
8488				Players.LocalPlayer.DevComputerMovementMode == Enum.DevComputerMovementMode.UserChoice,
8489				self.touchJumpController
8490			)
8491		elseif self.touchControlFrame then
8492			self.activeController:Enable(true, self.touchControlFrame)
8493		else
8494			self.activeController:Enable(true)
8495		end
8496	end
8497	
8498	function ControlModule:Enable(enable)
8499		if not self.activeController then
8500			return
8501		end
8502	
8503		if enable == nil then
8504			enable = true
8505		end
8506		if enable then
8507			self:EnableActiveControlModule()
8508		else
8509			self:Disable()
8510		end
8511	end
8512	
8513	-- For those who prefer distinct functions
8514	function ControlModule:Disable()
8515		if self.activeController then
8516			self.activeController:Enable(false)
8517	
8518			if self.moveFunction then
8519				self.moveFunction(Players.LocalPlayer, Vector3.new(0,0,0), true)
8520			end
8521		end
8522	end
8523	
8524	
8525	-- Returns module (possibly nil) and success code to differentiate returning nil due to error vs Scriptable
8526	function ControlModule:SelectComputerMovementModule()
8527		if not (UserInputService.KeyboardEnabled or UserInputService.GamepadEnabled) then
8528			return nil, false
8529		end
8530	
8531		local computerModule
8532		local DevMovementMode = Players.LocalPlayer.DevComputerMovementMode
8533	
8534		if DevMovementMode == Enum.DevComputerMovementMode.UserChoice then
8535			computerModule = computerInputTypeToModuleMap[lastInputType]
8536			if UserGameSettings.ComputerMovementMode == Enum.ComputerMovementMode.ClickToMove and computerModule == Keyboard then
8537				-- User has ClickToMove set in Settings, prefer ClickToMove controller for keyboard and mouse lastInputTypes
8538				computerModule = ClickToMove
8539			end
8540		else
8541			-- Developer has selected a mode that must be used.
8542			computerModule = movementEnumToModuleMap[DevMovementMode]
8543	
8544			-- computerModule is expected to be nil here only when developer has selected Scriptable
8545			if (not computerModule) and DevMovementMode ~= Enum.DevComputerMovementMode.Scriptable then
8546				warn("No character control module is associated with DevComputerMovementMode ", DevMovementMode)
8547			end
8548		end
8549	
8550		if computerModule then
8551			return computerModule, true
8552		elseif DevMovementMode == Enum.DevComputerMovementMode.Scriptable then
8553			-- Special case where nil is returned and we actually want to set self.activeController to nil for Scriptable
8554			return nil, true
8555		else
8556			-- This case is for when computerModule is nil because of an error and no suitable control module could
8557			-- be found.
8558			return nil, false
8559		end
8560	end
8561	
8562	-- Choose current Touch control module based on settings (user, dev)
8563	-- Returns module (possibly nil) and success code to differentiate returning nil due to error vs Scriptable
8564	function ControlModule:SelectTouchModule()
8565		if not UserInputService.TouchEnabled then
8566			return nil, false
8567		end
8568		local touchModule
8569		local DevMovementMode = Players.LocalPlayer.DevTouchMovementMode
8570		if DevMovementMode == Enum.DevTouchMovementMode.UserChoice then
8571			touchModule = movementEnumToModuleMap[UserGameSettings.TouchMovementMode]
8572		elseif DevMovementMode == Enum.DevTouchMovementMode.Scriptable then
8573			return nil, true
8574		else
8575			touchModule = movementEnumToModuleMap[DevMovementMode]
8576		end
8577		return touchModule, true
8578	end
8579	
8580	local function calculateRawMoveVector(humanoid, cameraRelativeMoveVector)
8581		local camera = Workspace.CurrentCamera
8582		if not camera then
8583			return cameraRelativeMoveVector
8584		end
8585	
8586		if humanoid:GetState() == Enum.HumanoidStateType.Swimming then
8587			return camera.CFrame:VectorToWorldSpace(cameraRelativeMoveVector)
8588		end
8589	
8590		local c, s
8591		local _, _, _, R00, R01, R02, _, _, R12, _, _, R22 = camera.CFrame:GetComponents()
8592		if R12 < 1 and R12 > -1 then
8593			-- X and Z components from back vector.
8594			c = R22
8595			s = R02
8596		else
8597			-- In this case the camera is looking straight up or straight down.
8598			-- Use X components from right and up vectors.
8599			c = R00
8600			s = -R01*math.sign(R12)
8601		end
8602		local norm = math.sqrt(c*c + s*s)
8603		return Vector3.new(
8604			(c*cameraRelativeMoveVector.x + s*cameraRelativeMoveVector.z)/norm,
8605			0,
8606			(c*cameraRelativeMoveVector.z - s*cameraRelativeMoveVector.x)/norm
8607		)
8608	end
8609	
8610	function ControlModule:OnRenderStepped(dt)
8611		if self.activeController and self.activeController.enabled and self.humanoid then
8612			-- Give the controller a chance to adjust its state
8613			self.activeController:OnRenderStepped(dt)
8614	
8615			-- Now retrieve info from the controller
8616			local moveVector = self.activeController:GetMoveVector()
8617			local cameraRelative = self.activeController:IsMoveVectorCameraRelative()
8618	
8619			local clickToMoveController = self:GetClickToMoveController()
8620			if self.activeController ~= clickToMoveController then
8621				if moveVector.magnitude > 0 then
8622					-- Clean up any developer started MoveTo path
8623					clickToMoveController:CleanupPath()
8624				else
8625					-- Get move vector for developer started MoveTo
8626					clickToMoveController:OnRenderStepped(dt)
8627					moveVector = clickToMoveController:GetMoveVector()
8628					cameraRelative = clickToMoveController:IsMoveVectorCameraRelative()
8629				end
8630			end
8631	
8632			-- Are we driving a vehicle ?
8633			local vehicleConsumedInput = false
8634			if self.vehicleController then
8635				moveVector, vehicleConsumedInput = self.vehicleController:Update(moveVector, cameraRelative, self.activeControlModule==Gamepad)
8636			end
8637	
8638			-- If not, move the player
8639			-- Verification of vehicleConsumedInput is commented out to preserve legacy behavior,
8640			-- in case some game relies on Humanoid.MoveDirection still being set while in a VehicleSeat
8641			--if not vehicleConsumedInput then
8642				if cameraRelative then
8643					moveVector = calculateRawMoveVector(self.humanoid, moveVector)
8644				end
8645				self.moveFunction(Players.LocalPlayer, moveVector, false)
8646			--end
8647	
8648			-- And make them jump if needed
8649			self.humanoid.Jump = self.activeController:GetIsJumping() or (self.touchJumpController and self.touchJumpController:GetIsJumping())
8650		end
8651	end
8652	
8653	function ControlModule:OnHumanoidSeated(active, currentSeatPart)
8654		if active then
8655			if currentSeatPart and currentSeatPart:IsA("VehicleSeat") then
8656				if not self.vehicleController then
8657					self.vehicleController = self.vehicleController.new(CONTROL_ACTION_PRIORITY)
8658				end
8659				self.vehicleController:Enable(true, currentSeatPart)
8660			end
8661		else
8662			if self.vehicleController then
8663				self.vehicleController:Enable(false, currentSeatPart)
8664			end
8665		end
8666	end
8667	
8668	function ControlModule:OnCharacterAdded(char)
8669		self.humanoid = char:FindFirstChildOfClass("Humanoid")
8670		while not self.humanoid do
8671			char.ChildAdded:wait()
8672			self.humanoid = char:FindFirstChildOfClass("Humanoid")
8673		end
8674	
8675		if self.touchGui then
8676			self.touchGui.Enabled = true
8677		end
8678	
8679		if self.humanoidSeatedConn then
8680			self.humanoidSeatedConn:Disconnect()
8681			self.humanoidSeatedConn = nil
8682		end
8683		self.humanoidSeatedConn = self.humanoid.Seated:Connect(function(active, currentSeatPart)
8684			self:OnHumanoidSeated(active, currentSeatPart)
8685		end)
8686	end
8687	
8688	function ControlModule:OnCharacterRemoving(char)
8689		self.humanoid = nil
8690	
8691		if self.touchGui then
8692			self.touchGui.Enabled = false
8693		end
8694	end
8695	
8696	-- Helper function to lazily instantiate a controller if it does not yet exist,
8697	-- disable the active controller if it is different from the on being switched to,
8698	-- and then enable the requested controller. The argument to this function must be
8699	-- a reference to one of the control modules, i.e. Keyboard, Gamepad, etc.
8700	function ControlModule:SwitchToController(controlModule)
8701		if not controlModule then
8702			if self.activeController then
8703				self.activeController:Enable(false)
8704			end
8705			self.activeController = nil
8706			self.activeControlModule = nil
8707		else
8708			if not self.controllers[controlModule] then
8709				self.controllers[controlModule] = controlModule.new(CONTROL_ACTION_PRIORITY)
8710			end
8711	
8712			if self.activeController ~= self.controllers[controlModule] then
8713				if self.activeController then
8714					self.activeController:Enable(false)
8715				end
8716				self.activeController = self.controllers[controlModule]
8717				self.activeControlModule = controlModule -- Only used to check if controller switch is necessary
8718	
8719				if self.touchControlFrame and (self.activeControlModule == ClickToMove
8720							or self.activeControlModule == TouchThumbstick
8721							or self.activeControlModule == DynamicThumbstick) then
8722					if not self.controllers[TouchJump] then
8723						self.controllers[TouchJump] = TouchJump.new()
8724					end
8725					self.touchJumpController = self.controllers[TouchJump]
8726					self.touchJumpController:Enable(true, self.touchControlFrame)
8727				else
8728					if self.touchJumpController then
8729						self.touchJumpController:Enable(false)
8730					end
8731				end
8732	
8733				self:EnableActiveControlModule()
8734			end
8735		end
8736	end
8737	
8738	function ControlModule:OnLastInputTypeChanged(newLastInputType)
8739		if lastInputType == newLastInputType then
8740			warn("LastInputType Change listener called with current type.")
8741		end
8742		lastInputType = newLastInputType
8743	
8744		if lastInputType == Enum.UserInputType.Touch then
8745			-- TODO: Check if touch module already active
8746			local touchModule, success = self:SelectTouchModule()
8747			if success then
8748				while not self.touchControlFrame do
8749					wait()
8750				end
8751				self:SwitchToController(touchModule)
8752			end
8753		elseif computerInputTypeToModuleMap[lastInputType] ~= nil then
8754			local computerModule = self:SelectComputerMovementModule()
8755			if computerModule then
8756				self:SwitchToController(computerModule)
8757			end
8758		end
8759	end
8760	
8761	-- Called when any relevant values of GameSettings or LocalPlayer change, forcing re-evalulation of
8762	-- current control scheme
8763	function ControlModule:OnComputerMovementModeChange()
8764		local controlModule, success =  self:SelectComputerMovementModule()
8765		if success then
8766			self:SwitchToController(controlModule)
8767		end
8768	end
8769	
8770	function ControlModule:OnTouchMovementModeChange()
8771		local touchModule, success = self:SelectTouchModule()
8772		if success then
8773			while not self.touchControlFrame do
8774				wait()
8775			end
8776			self:SwitchToController(touchModule)
8777		end
8778	end
8779	
8780	function ControlModule:CreateTouchGuiContainer()
8781		if self.touchGui then self.touchGui:Destroy() end
8782	
8783		-- Container for all touch device guis
8784		self.touchGui = Instance.new("ScreenGui")
8785		self.touchGui.Name = "TouchGui"
8786		self.touchGui.ResetOnSpawn = false
8787		self.touchGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
8788		self.touchGui.Enabled = self.humanoid ~= nil
8789	
8790		self.touchControlFrame = Instance.new("Frame")
8791		self.touchControlFrame.Name = "TouchControlFrame"
8792		self.touchControlFrame.Size = UDim2.new(1, 0, 1, 0)
8793		self.touchControlFrame.BackgroundTransparency = 1
8794		self.touchControlFrame.Parent = self.touchGui
8795	
8796		self.touchGui.Parent = self.playerGui
8797	end
8798	
8799	function ControlModule:GetClickToMoveController()
8800		if not self.controllers[ClickToMove] then
8801			self.controllers[ClickToMove] = ClickToMove.new(CONTROL_ACTION_PRIORITY)
8802		end
8803		return self.controllers[ClickToMove]
8804	end
8805	
8806	function ControlModule:IsJumping()
8807		if self.activeController then
8808			return self.activeController:GetIsJumping() or (self.touchJumpController and self.touchJumpController:GetIsJumping())
8809		end
8810		return false
8811	end
8812	
8813	return ControlModule.new()
8814end
8815
8816function _PlayerModule()
8817	local PlayerModule = {}
8818	PlayerModule.__index = PlayerModule
8819	function PlayerModule.new()
8820		local self = setmetatable({},PlayerModule)
8821		self.cameras = _CameraModule()
8822		self.controls = _ControlModule()
8823		return self
8824	end
8825	function PlayerModule:GetCameras()
8826		return self.cameras
8827	end
8828	function PlayerModule:GetControls()
8829		return self.controls
8830	end
8831	function PlayerModule:GetClickToMoveController()
8832		return self.controls:GetClickToMoveController()
8833	end
8834	return PlayerModule.new()
8835end
8836
8837function _sounds()
8838	
8839	local SetState = Instance.new("BindableEvent",script)
8840	
8841	local Players = game:GetService("Players")
8842	local RunService = game:GetService("RunService")
8843	
8844	local SOUND_DATA = {
8845		Climbing = {
8846			SoundId = "rbxasset://sounds/action_footsteps_plastic.mp3",
8847			Looped = true,
8848		},
8849		Died = {
8850			SoundId = "rbxasset://sounds/uuhhh.mp3",
8851		},
8852		FreeFalling = {
8853			SoundId = "rbxasset://sounds/action_falling.mp3",
8854			Looped = true,
8855		},
8856		GettingUp = {
8857			SoundId = "rbxasset://sounds/action_get_up.mp3",
8858		},
8859		Jumping = {
8860			SoundId = "rbxasset://sounds/action_jump.mp3",
8861		},
8862		Landing = {
8863			SoundId = "rbxasset://sounds/action_jump_land.mp3",
8864		},
8865		Running = {
8866			SoundId = "rbxasset://sounds/action_footsteps_plastic.mp3",
8867			Looped = true,
8868			Pitch = 1.85,
8869		},
8870		Splash = {
8871			SoundId = "rbxasset://sounds/impact_water.mp3",
8872		},
8873		Swimming = {
8874			SoundId = "rbxasset://sounds/action_swim.mp3",
8875			Looped = true,
8876			Pitch = 1.6,
8877		},
8878	}
8879	
8880	 -- wait for the first of the passed signals to fire
8881	local function waitForFirst(...)
8882		local shunt = Instance.new("BindableEvent")
8883		local slots = {...}
8884	
8885		local function fire(...)
8886			for i = 1, #slots do
8887				slots[i]:Disconnect()
8888			end
8889	
8890			return shunt:Fire(...)
8891		end
8892	
8893		for i = 1, #slots do
8894			slots[i] = slots[i]:Connect(fire)
8895		end
8896	
8897		return shunt.Event:Wait()
8898	end
8899	
8900	-- map a value from one range to another
8901	local function map(x, inMin, inMax, outMin, outMax)
8902		return (x - inMin)*(outMax - outMin)/(inMax - inMin) + outMin
8903	end
8904	
8905	local function playSound(sound)
8906		sound.TimePosition = 0
8907		sound.Playing = true
8908	end
8909	
8910	local function stopSound(sound)
8911		sound.Playing = false
8912		sound.TimePosition = 0
8913	end
8914	
8915	local function shallowCopy(t)
8916		local out = {}
8917		for k, v in pairs(t) do
8918			out[k] = v
8919		end
8920		return out
8921	end
8922	
8923	local function initializeSoundSystem(player, humanoid, rootPart)
8924		local sounds = {}
8925	
8926		-- initialize sounds
8927		for name, props in pairs(SOUND_DATA) do
8928			local sound = Instance.new("Sound")
8929			sound.Name = name
8930	
8931			-- set default values
8932			sound.Archivable = false
8933			sound.EmitterSize = 5
8934			sound.MaxDistance = 150
8935			sound.Volume = 0.65
8936	
8937			for propName, propValue in pairs(props) do
8938				sound[propName] = propValue
8939			end
8940	
8941			sound.Parent = rootPart
8942			sounds[name] = sound
8943		end
8944	
8945		local playingLoopedSounds = {}
8946	
8947		local function stopPlayingLoopedSounds(except)
8948			for sound in pairs(shallowCopy(playingLoopedSounds)) do
8949				if sound ~= except then
8950					sound.Playing = false
8951					playingLoopedSounds[sound] = nil
8952				end
8953			end
8954		end
8955	
8956		-- state transition callbacks
8957		local stateTransitions = {
8958			[Enum.HumanoidStateType.FallingDown] = function()
8959				stopPlayingLoopedSounds()
8960			end,
8961	
8962			[Enum.HumanoidStateType.GettingUp] = function()
8963				stopPlayingLoopedSounds()
8964				playSound(sounds.GettingUp)
8965			end,
8966	
8967			[Enum.HumanoidStateType.Jumping] = function()
8968				stopPlayingLoopedSounds()
8969				playSound(sounds.Jumping)
8970			end,
8971	
8972			[Enum.HumanoidStateType.Swimming] = function()
8973				local verticalSpeed = math.abs(rootPart.Velocity.Y)
8974				if verticalSpeed > 0.1 then
8975					sounds.Splash.Volume = math.clamp(map(verticalSpeed, 100, 350, 0.28, 1), 0, 1)
8976					playSound(sounds.Splash)
8977				end
8978				stopPlayingLoopedSounds(sounds.Swimming)
8979				sounds.Swimming.Playing = true
8980				playingLoopedSounds[sounds.Swimming] = true
8981			end,
8982	
8983			[Enum.HumanoidStateType.Freefall] = function()
8984				sounds.FreeFalling.Volume = 0
8985				stopPlayingLoopedSounds(sounds.FreeFalling)
8986				playingLoopedSounds[sounds.FreeFalling] = true
8987			end,
8988	
8989			[Enum.HumanoidStateType.Landed] = function()
8990				stopPlayingLoopedSounds()
8991				local verticalSpeed = math.abs(rootPart.Velocity.Y)
8992				if verticalSpeed > 75 then
8993					sounds.Landing.Volume = math.clamp(map(verticalSpeed, 50, 100, 0, 1), 0, 1)
8994					playSound(sounds.Landing)
8995				end
8996			end,
8997	
8998			[Enum.HumanoidStateType.Running] = function()
8999				stopPlayingLoopedSounds(sounds.Running)
9000				sounds.Running.Playing = true
9001				playingLoopedSounds[sounds.Running] = true
9002			end,
9003	
9004			[Enum.HumanoidStateType.Climbing] = function()
9005				local sound = sounds.Climbing
9006				if math.abs(rootPart.Velocity.Y) > 0.1 then
9007					sound.Playing = true
9008					stopPlayingLoopedSounds(sound)
9009				else
9010					stopPlayingLoopedSounds()
9011				end
9012				playingLoopedSounds[sound] = true
9013			end,
9014	
9015			[Enum.HumanoidStateType.Seated] = function()
9016				stopPlayingLoopedSounds()
9017			end,
9018	
9019			[Enum.HumanoidStateType.Dead] = function()
9020				stopPlayingLoopedSounds()
9021				playSound(sounds.Died)
9022			end,
9023		}
9024	
9025		-- updaters for looped sounds
9026		local loopedSoundUpdaters = {
9027			[sounds.Climbing] = function(dt, sound, vel)
9028				sound.Playing = vel.Magnitude > 0.1
9029			end,
9030	
9031			[sounds.FreeFalling] = function(dt, sound, vel)
9032				if vel.Magnitude > 75 then
9033					sound.Volume = math.clamp(sound.Volume + 0.9*dt, 0, 1)
9034				else
9035					sound.Volume = 0
9036				end
9037			end,
9038	
9039			[sounds.Running] = function(dt, sound, vel)
9040				sound.Playing = vel.Magnitude > 0.5 and humanoid.MoveDirection.Magnitude > 0.5
9041			end,
9042		}
9043	
9044		-- state substitutions to avoid duplicating entries in the state table
9045		local stateRemap = {
9046			[Enum.HumanoidStateType.RunningNoPhysics] = Enum.HumanoidStateType.Running,
9047		}
9048	
9049		local activeState = stateRemap[humanoid:GetState()] or humanoid:GetState()
9050		local activeConnections = {}
9051	
9052		local stateChangedConn = humanoid.StateChanged:Connect(function(_, state)
9053			state = stateRemap[state] or state
9054	
9055			if state ~= activeState then
9056				local transitionFunc = stateTransitions[state]
9057	
9058				if transitionFunc then
9059					transitionFunc()
9060				end
9061	
9062				activeState = state
9063			end
9064		end)
9065		
9066		local customStateChangedConn = SetState.Event:Connect(function(state)
9067			state = stateRemap[state] or state
9068	
9069			if state ~= activeState then
9070				local transitionFunc = stateTransitions[state]
9071	
9072				if transitionFunc then
9073					transitionFunc()
9074				end
9075	
9076				activeState = state
9077			end
9078		end)
9079	
9080		local steppedConn = RunService.Stepped:Connect(function(_, worldDt)
9081			-- update looped sounds on stepped
9082			for sound in pairs(playingLoopedSounds) do
9083				local updater = loopedSoundUpdaters[sound]
9084	
9085				if updater then
9086					updater(worldDt, sound, rootPart.Velocity)
9087				end
9088			end
9089		end)
9090	
9091		local humanoidAncestryChangedConn
9092		local rootPartAncestryChangedConn
9093		local characterAddedConn
9094	
9095		local function terminate()
9096			stateChangedConn:Disconnect()
9097			customStateChangedConn:Disconnect()
9098			steppedConn:Disconnect()
9099			humanoidAncestryChangedConn:Disconnect()
9100			rootPartAncestryChangedConn:Disconnect()
9101			characterAddedConn:Disconnect()
9102		end
9103	
9104		humanoidAncestryChangedConn = humanoid.AncestryChanged:Connect(function(_, parent)
9105			if not parent then
9106				terminate()
9107			end
9108		end)
9109	
9110		rootPartAncestryChangedConn = rootPart.AncestryChanged:Connect(function(_, parent)
9111			if not parent then
9112				terminate()
9113			end
9114		end)
9115	
9116		characterAddedConn = player.CharacterAdded:Connect(terminate)
9117	end
9118	
9119	local function playerAdded(player)
9120		local function characterAdded(character)
9121			-- Avoiding memory leaks in the face of Character/Humanoid/RootPart lifetime has a few complications:
9122			-- * character deparenting is a Remove instead of a Destroy, so signals are not cleaned up automatically.
9123			-- ** must use a waitForFirst on everything and listen for hierarchy changes.
9124			-- * the character might not be in the dm by the time CharacterAdded fires
9125			-- ** constantly check consistency with player.Character and abort if CharacterAdded is fired again
9126			-- * Humanoid may not exist immediately, and by the time it's inserted the character might be deparented.
9127			-- * RootPart probably won't exist immediately.
9128			-- ** by the time RootPart is inserted and Humanoid.RootPart is set, the character or the humanoid might be deparented.
9129	
9130			if not character.Parent then
9131				waitForFirst(character.AncestryChanged, player.CharacterAdded)
9132			end
9133	
9134			if player.Character ~= character or not character.Parent then
9135				return
9136			end
9137	
9138			local humanoid = character:FindFirstChildOfClass("Humanoid")
9139			while character:IsDescendantOf(game) and not humanoid do
9140				waitForFirst(character.ChildAdded, character.AncestryChanged, player.CharacterAdded)
9141				humanoid = character:FindFirstChildOfClass("Humanoid")
9142			end
9143	
9144			if player.Character ~= character or not character:IsDescendantOf(game) then
9145				return
9146			end
9147	
9148			-- must rely on HumanoidRootPart naming because Humanoid.RootPart does not fire changed signals
9149			local rootPart = character:FindFirstChild("HumanoidRootPart")
9150			while character:IsDescendantOf(game) and not rootPart do
9151				waitForFirst(character.ChildAdded, character.AncestryChanged, humanoid.AncestryChanged, player.CharacterAdded)
9152				rootPart = character:FindFirstChild("HumanoidRootPart")
9153			end
9154	
9155			if rootPart and humanoid:IsDescendantOf(game) and character:IsDescendantOf(game) and player.Character == character then
9156				initializeSoundSystem(player, humanoid, rootPart)
9157			end
9158		end
9159	
9160		if player.Character then
9161			characterAdded(player.Character)
9162		end
9163		player.CharacterAdded:Connect(characterAdded)
9164	end
9165	
9166	Players.PlayerAdded:Connect(playerAdded)
9167	for _, player in ipairs(Players:GetPlayers()) do
9168		playerAdded(player)
9169	end
9170	return SetState
9171end
9172
9173function _StateTracker()
9174	local EPSILON = 0.1
9175	
9176	local SPEED = {
9177		["onRunning"] = true,
9178		["onClimbing"] = true 
9179	}
9180	
9181	local INAIR = {
9182		["onFreeFall"] = true,
9183		["onJumping"] = true
9184	}
9185	
9186	local STATEMAP = {
9187		["onRunning"] = Enum.HumanoidStateType.Running,
9188		["onJumping"] = Enum.HumanoidStateType.Jumping,
9189		["onFreeFall"] = Enum.HumanoidStateType.Freefall
9190	}
9191	
9192	local StateTracker = {}
9193	StateTracker.__index = StateTracker
9194	
9195	function StateTracker.new(humanoid, soundState)
9196		local self = setmetatable({}, StateTracker)
9197		
9198		self.Humanoid = humanoid
9199		self.HRP = humanoid.RootPart
9200		
9201		self.Speed = 0
9202		self.State = "onRunning"
9203		self.Jumped = false
9204		self.JumpTick = tick()
9205		
9206		self.SoundState = soundState
9207		
9208		self._ChangedEvent = Instance.new("BindableEvent")
9209		self.Changed = self._ChangedEvent.Event
9210		
9211		return self
9212	end
9213	
9214	function StateTracker:Destroy()
9215		self._ChangedEvent:Destroy()
9216	end
9217	
9218	function StateTracker:RequestedJump()
9219		self.Jumped = true
9220		self.JumpTick = tick()
9221	end
9222	
9223	function StateTracker:OnStep(gravityUp, grounded, isMoving)
9224		local cVelocity = self.HRP.Velocity
9225		local gVelocity = cVelocity:Dot(gravityUp)
9226		
9227		local oldState, oldSpeed = self.State, self.Speed
9228		
9229		local newState
9230		local newSpeed = cVelocity.Magnitude
9231	
9232		if (not grounded) then
9233			if (gVelocity > 0) then
9234				if (self.Jumped) then
9235					newState = "onJumping"
9236				else
9237					newState = "onFreeFall"
9238				end
9239			else
9240				if (self.Jumped) then
9241					self.Jumped = false
9242				end
9243				newState = "onFreeFall"
9244			end
9245		else
9246			if (self.Jumped and tick() - self.JumpTick > 0.1) then
9247				self.Jumped = false
9248			end
9249			newSpeed = (cVelocity - gVelocity*gravityUp).Magnitude
9250			newState = "onRunning"
9251		end
9252		
9253		newSpeed = isMoving and newSpeed or 0
9254		
9255		if (oldState ~= newState or (SPEED[newState] and math.abs(oldSpeed - newSpeed) > EPSILON)) then
9256			self.State = newState
9257			self.Speed = newSpeed
9258			self.SoundState:Fire(STATEMAP[newState])
9259			self._ChangedEvent:Fire(self.State, self.Speed)
9260		end
9261	end
9262	
9263	return StateTracker
9264end
9265function _InitObjects()
9266	local model = workspace:FindFirstChild("objects") or game:GetObjects("rbxassetid://5045408489")[1]
9267	local SPHERE = model:WaitForChild("Sphere")
9268	local FLOOR = model:WaitForChild("Floor")
9269	local VFORCE = model:WaitForChild("VectorForce")
9270	local BGYRO = model:WaitForChild("BodyGyro")
9271	local function initObjects(self)
9272		local hrp = self.HRP
9273		local humanoid = self.Humanoid
9274		local sphere = SPHERE:Clone()
9275		sphere.Parent = self.Character
9276		local floor = FLOOR:Clone()
9277		floor.Parent = self.Character
9278		local isR15 = (humanoid.RigType == Enum.HumanoidRigType.R15)
9279		local height = isR15 and (humanoid.HipHeight + 0.05) or 2
9280		local weld = Instance.new("Weld")
9281		weld.C0 = CFrame.new(0, -height, 0.1)
9282		weld.Part0 = hrp
9283		weld.Part1 = sphere
9284		weld.Parent = sphere
9285		local weld2 = Instance.new("Weld")
9286		weld2.C0 = CFrame.new(0, -(height + 1.5), 0)
9287		weld2.Part0 = hrp
9288		weld2.Part1 = floor
9289		weld2.Parent = floor
9290		local gyro = BGYRO:Clone()
9291		gyro.CFrame = hrp.CFrame
9292		gyro.Parent = hrp
9293		local vForce = VFORCE:Clone()
9294		vForce.Attachment0 = isR15 and hrp:WaitForChild("RootRigAttachment") or hrp:WaitForChild("RootAttachment")
9295		vForce.Parent = hrp
9296		return sphere, gyro, vForce, floor
9297	end
9298	return initObjects
9299end
9300local plr = game.Players.LocalPlayer
9301local ms = plr:GetMouse()
9302local char
9303plr.CharacterAdded:Connect(function(c)
9304	char = c
9305end)
9306function _R6()
9307	function r6()
9308	local Figure = char
9309	local Torso = Figure:WaitForChild("Torso")
9310	local RightShoulder = Torso:WaitForChild("Right Shoulder")
9311	local LeftShoulder = Torso:WaitForChild("Left Shoulder")
9312	local RightHip = Torso:WaitForChild("Right Hip")
9313	local LeftHip = Torso:WaitForChild("Left Hip")
9314	local Neck = Torso:WaitForChild("Neck")
9315	local Humanoid = Figure:WaitForChild("Humanoid")
9316	local pose = "Standing"
9317	local currentAnim = ""
9318	local currentAnimInstance = nil
9319	local currentAnimTrack = nil
9320	local currentAnimKeyframeHandler = nil
9321	local currentAnimSpeed = 1.0
9322	local animTable = {}
9323	local animNames = { 
9324		idle = 	{	
9325					{ id = "http://www.roblox.com/asset/?id=180435571", weight = 9 },
9326					{ id = "http://www.roblox.com/asset/?id=180435792", weight = 1 }
9327				},
9328		walk = 	{ 	
9329					{ id = "http://www.roblox.com/asset/?id=180426354", weight = 10 } 
9330				}, 
9331		run = 	{
9332					{ id = "run.xml", weight = 10 } 
9333				}, 
9334		jump = 	{
9335					{ id = "http://www.roblox.com/asset/?id=125750702", weight = 10 } 
9336				}, 
9337		fall = 	{
9338					{ id = "http://www.roblox.com/asset/?id=180436148", weight = 10 } 
9339				}, 
9340		climb = {
9341					{ id = "http://www.roblox.com/asset/?id=180436334", weight = 10 } 
9342				}, 
9343		sit = 	{
9344					{ id = "http://www.roblox.com/asset/?id=178130996", weight = 10 } 
9345				},	
9346		toolnone = {
9347					{ id = "http://www.roblox.com/asset/?id=182393478", weight = 10 } 
9348				},
9349		toolslash = {
9350					{ id = "http://www.roblox.com/asset/?id=129967390", weight = 10 } 
9351	--				{ id = "slash.xml", weight = 10 } 
9352				},
9353		toollunge = {
9354					{ id = "http://www.roblox.com/asset/?id=129967478", weight = 10 } 
9355				},
9356		wave = {
9357					{ id = "http://www.roblox.com/asset/?id=128777973", weight = 10 } 
9358				},
9359		point = {
9360					{ id = "http://www.roblox.com/asset/?id=128853357", weight = 10 } 
9361				},
9362		dance1 = {
9363					{ id = "http://www.roblox.com/asset/?id=182435998", weight = 10 }, 
9364					{ id = "http://www.roblox.com/asset/?id=182491037", weight = 10 }, 
9365					{ id = "http://www.roblox.com/asset/?id=182491065", weight = 10 } 
9366				},
9367		dance2 = {
9368					{ id = "http://www.roblox.com/asset/?id=182436842", weight = 10 }, 
9369					{ id = "http://www.roblox.com/asset/?id=182491248", weight = 10 }, 
9370					{ id = "http://www.roblox.com/asset/?id=182491277", weight = 10 } 
9371				},
9372		dance3 = {
9373					{ id = "http://www.roblox.com/asset/?id=182436935", weight = 10 }, 
9374					{ id = "http://www.roblox.com/asset/?id=182491368", weight = 10 }, 
9375					{ id = "http://www.roblox.com/asset/?id=182491423", weight = 10 } 
9376				},
9377		laugh = {
9378					{ id = "http://www.roblox.com/asset/?id=129423131", weight = 10 } 
9379				},
9380		cheer = {
9381					{ id = "http://www.roblox.com/asset/?id=129423030", weight = 10 } 
9382				},
9383	}
9384	local dances = {"dance1", "dance2", "dance3"}
9385	-- Existance in this list signifies that it is an emote, the value indicates if it is a looping emote
9386	local emoteNames = { wave = false, point = false, dance1 = true, dance2 = true, dance3 = true, laugh = false, cheer = false}
9387	function configureAnimationSet(name, fileList)
9388		if (animTable[name] ~= nil) then
9389			for _, connection in pairs(animTable[name].connections) do
9390				connection:disconnect()
9391			end
9392		end
9393		animTable[name] = {}
9394		animTable[name].count = 0
9395		animTable[name].totalWeight = 0	
9396		animTable[name].connections = {}
9397		-- check for config values
9398		local config = script:FindFirstChild(name)
9399		if (config ~= nil) then
9400	--		print("Loading anims " .. name)
9401			table.insert(animTable[name].connections, config.ChildAdded:connect(function(child) configureAnimationSet(name, fileList) end))
9402			table.insert(animTable[name].connections, config.ChildRemoved:connect(function(child) configureAnimationSet(name, fileList) end))
9403			local idx = 1
9404			for _, childPart in pairs(config:GetChildren()) do
9405				if (childPart:IsA("Animation")) then
9406					table.insert(animTable[name].connections, childPart.Changed:connect(function(property) configureAnimationSet(name, fileList) end))
9407					animTable[name][idx] = {}
9408					animTable[name][idx].anim = childPart
9409					local weightObject = childPart:FindFirstChild("Weight")
9410					if (weightObject == nil) then
9411						animTable[name][idx].weight = 1
9412					else
9413						animTable[name][idx].weight = weightObject.Value
9414					end
9415					animTable[name].count = animTable[name].count + 1
9416					animTable[name].totalWeight = animTable[name].totalWeight + animTable[name][idx].weight
9417		--			print(name .. " [" .. idx .. "] " .. animTable[name][idx].anim.AnimationId .. " (" .. animTable[name][idx].weight .. ")")
9418					idx = idx + 1
9419				end
9420			end
9421		end
9422		-- fallback to defaults
9423		if (animTable[name].count <= 0) then
9424			for idx, anim in pairs(fileList) do
9425				animTable[name][idx] = {}
9426				animTable[name][idx].anim = Instance.new("Animation")
9427				animTable[name][idx].anim.Name = name
9428				animTable[name][idx].anim.AnimationId = anim.id
9429				animTable[name][idx].weight = anim.weight
9430				animTable[name].count = animTable[name].count + 1
9431				animTable[name].totalWeight = animTable[name].totalWeight + anim.weight
9432	--			print(name .. " [" .. idx .. "] " .. anim.id .. " (" .. anim.weight .. ")")
9433			end
9434		end
9435	end
9436	-- Setup animation objects
9437	function scriptChildModified(child)
9438		local fileList = animNames[child.Name]
9439		if (fileList ~= nil) then
9440			configureAnimationSet(child.Name, fileList)
9441		end	
9442	end
9443	
9444	script.ChildAdded:connect(scriptChildModified)
9445	script.ChildRemoved:connect(scriptChildModified)
9446	
9447	
9448	for name, fileList in pairs(animNames) do 
9449		configureAnimationSet(name, fileList)
9450	end	
9451	
9452	-- ANIMATION
9453	
9454	-- declarations
9455	local toolAnim = "None"
9456	local toolAnimTime = 0
9457	
9458	local jumpAnimTime = 0
9459	local jumpAnimDuration = 0.3
9460	
9461	local toolTransitionTime = 0.1
9462	local fallTransitionTime = 0.3
9463	local jumpMaxLimbVelocity = 0.75
9464	
9465	-- functions
9466	
9467	function stopAllAnimations()
9468		local oldAnim = currentAnim
9469	
9470		-- return to idle if finishing an emote
9471		if (emoteNames[oldAnim] ~= nil and emoteNames[oldAnim] == false) then
9472			oldAnim = "idle"
9473		end
9474	
9475		currentAnim = ""
9476		currentAnimInstance = nil
9477		if (currentAnimKeyframeHandler ~= nil) then
9478			currentAnimKeyframeHandler:disconnect()
9479		end
9480	
9481		if (currentAnimTrack ~= nil) then
9482			currentAnimTrack:Stop()
9483			currentAnimTrack:Destroy()
9484			currentAnimTrack = nil
9485		end
9486		return oldAnim
9487	end
9488	
9489	function setAnimationSpeed(speed)
9490		if speed ~= currentAnimSpeed then
9491			currentAnimSpeed = speed
9492			currentAnimTrack:AdjustSpeed(currentAnimSpeed)
9493		end
9494	end
9495	
9496	function keyFrameReachedFunc(frameName)
9497		if (frameName == "End") then
9498	
9499			local repeatAnim = currentAnim
9500			-- return to idle if finishing an emote
9501			if (emoteNames[repeatAnim] ~= nil and emoteNames[repeatAnim] == false) then
9502				repeatAnim = "idle"
9503			end
9504			
9505			local animSpeed = currentAnimSpeed
9506			playAnimation(repeatAnim, 0.0, Humanoid)
9507			setAnimationSpeed(animSpeed)
9508		end
9509	end
9510	
9511	-- Preload animations
9512	function playAnimation(animName, transitionTime, humanoid) 
9513			
9514		local roll = math.random(1, animTable[animName].totalWeight) 
9515		local origRoll = roll
9516		local idx = 1
9517		while (roll > animTable[animName][idx].weight) do
9518			roll = roll - animTable[animName][idx].weight
9519			idx = idx + 1
9520		end
9521	--		print(animName .. " " .. idx .. " [" .. origRoll .. "]")
9522		local anim = animTable[animName][idx].anim
9523	
9524		-- switch animation		
9525		if (anim ~= currentAnimInstance) then
9526			
9527			if (currentAnimTrack ~= nil) then
9528				currentAnimTrack:Stop(transitionTime)
9529				currentAnimTrack:Destroy()
9530			end
9531	
9532			currentAnimSpeed = 1.0
9533		
9534			-- load it to the humanoid; get AnimationTrack
9535			currentAnimTrack = humanoid:LoadAnimation(anim)
9536			currentAnimTrack.Priority = Enum.AnimationPriority.Core
9537			 
9538			-- play the animation
9539			currentAnimTrack:Play(transitionTime)
9540			currentAnim = animName
9541			currentAnimInstance = anim
9542	
9543			-- set up keyframe name triggers
9544			if (currentAnimKeyframeHandler ~= nil) then
9545				currentAnimKeyframeHandler:disconnect()
9546			end
9547			currentAnimKeyframeHandler = currentAnimTrack.KeyframeReached:connect(keyFrameReachedFunc)
9548			
9549		end
9550	
9551	end
9552	
9553	-------------------------------------------------------------------------------------------
9554	-------------------------------------------------------------------------------------------
9555	
9556	local toolAnimName = ""
9557	local toolAnimTrack = nil
9558	local toolAnimInstance = nil
9559	local currentToolAnimKeyframeHandler = nil
9560	
9561	function toolKeyFrameReachedFunc(frameName)
9562		if (frameName == "End") then
9563	--		print("Keyframe : ".. frameName)	
9564			playToolAnimation(toolAnimName, 0.0, Humanoid)
9565		end
9566	end
9567	
9568	
9569	function playToolAnimation(animName, transitionTime, humanoid, priority)	 
9570			
9571			local roll = math.random(1, animTable[animName].totalWeight) 
9572			local origRoll = roll
9573			local idx = 1
9574			while (roll > animTable[animName][idx].weight) do
9575				roll = roll - animTable[animName][idx].weight
9576				idx = idx + 1
9577			end
9578	--		print(animName .. " * " .. idx .. " [" .. origRoll .. "]")
9579			local anim = animTable[animName][idx].anim
9580	
9581			if (toolAnimInstance ~= anim) then
9582				
9583				if (toolAnimTrack ~= nil) then
9584					toolAnimTrack:Stop()
9585					toolAnimTrack:Destroy()
9586					transitionTime = 0
9587				end
9588						
9589				-- load it to the humanoid; get AnimationTrack
9590				toolAnimTrack = humanoid:LoadAnimation(anim)
9591				if priority then
9592					toolAnimTrack.Priority = priority
9593				end
9594				 
9595				-- play the animation
9596				toolAnimTrack:Play(transitionTime)
9597				toolAnimName = animName
9598				toolAnimInstance = anim
9599	
9600				currentToolAnimKeyframeHandler = toolAnimTrack.KeyframeReached:connect(toolKeyFrameReachedFunc)
9601			end
9602	end
9603	
9604	function stopToolAnimations()
9605		local oldAnim = toolAnimName
9606	
9607		if (currentToolAnimKeyframeHandler ~= nil) then
9608			currentToolAnimKeyframeHandler:disconnect()
9609		end
9610	
9611		toolAnimName = ""
9612		toolAnimInstance = nil
9613		if (toolAnimTrack ~= nil) then
9614			toolAnimTrack:Stop()
9615			toolAnimTrack:Destroy()
9616			toolAnimTrack = nil
9617		end
9618	
9619	
9620		return oldAnim
9621	end
9622	
9623	-------------------------------------------------------------------------------------------
9624	-------------------------------------------------------------------------------------------
9625	
9626	
9627	function onRunning(speed)
9628		if speed > 0.01 then
9629			playAnimation("walk", 0.1, Humanoid)
9630			if currentAnimInstance and currentAnimInstance.AnimationId == "http://www.roblox.com/asset/?id=180426354" then
9631				setAnimationSpeed(speed / 14.5)
9632			end
9633			pose = "Running"
9634		else
9635			if emoteNames[currentAnim] == nil then
9636				playAnimation("idle", 0.1, Humanoid)
9637				pose = "Standing"
9638			end
9639		end
9640	end
9641	
9642	function onDied()
9643		pose = "Dead"
9644	end
9645	
9646	function onJumping()
9647		playAnimation("jump", 0.1, Humanoid)
9648		jumpAnimTime = jumpAnimDuration
9649		pose = "Jumping"
9650	end
9651	
9652	function onClimbing(speed)
9653		playAnimation("climb", 0.1, Humanoid)
9654		setAnimationSpeed(speed / 12.0)
9655		pose = "Climbing"
9656	end
9657	
9658	function onGettingUp()
9659		pose = "GettingUp"
9660	end
9661	
9662	function onFreeFall()
9663		if (jumpAnimTime <= 0) then
9664			playAnimation("fall", fallTransitionTime, Humanoid)
9665		end
9666		pose = "FreeFall"
9667	end
9668	
9669	function onFallingDown()
9670		pose = "FallingDown"
9671	end
9672	
9673	function onSeated()
9674		pose = "Seated"
9675	end
9676	
9677	function onPlatformStanding()
9678		pose = "PlatformStanding"
9679	end
9680	
9681	function onSwimming(speed)
9682		if speed > 0 then
9683			pose = "Running"
9684		else
9685			pose = "Standing"
9686		end
9687	end
9688	
9689	function getTool()	
9690		for _, kid in ipairs(Figure:GetChildren()) do
9691			if kid.className == "Tool" then return kid end
9692		end
9693		return nil
9694	end
9695	
9696	function getToolAnim(tool)
9697		for _, c in ipairs(tool:GetChildren()) do
9698			if c.Name == "toolanim" and c.className == "StringValue" then
9699				return c
9700			end
9701		end
9702		return nil
9703	end
9704	
9705	function animateTool()
9706		
9707		if (toolAnim == "None") then
9708			playToolAnimation("toolnone", toolTransitionTime, Humanoid, Enum.AnimationPriority.Idle)
9709			return
9710		end
9711	
9712		if (toolAnim == "Slash") then
9713			playToolAnimation("toolslash", 0, Humanoid, Enum.AnimationPriority.Action)
9714			return
9715		end
9716	
9717		if (toolAnim == "Lunge") then
9718			playToolAnimation("toollunge", 0, Humanoid, Enum.AnimationPriority.Action)
9719			return
9720		end
9721	end
9722	
9723	function moveSit()
9724		RightShoulder.MaxVelocity = 0.15
9725		LeftShoulder.MaxVelocity = 0.15
9726		RightShoulder:SetDesiredAngle(3.14 /2)
9727		LeftShoulder:SetDesiredAngle(-3.14 /2)
9728		RightHip:SetDesiredAngle(3.14 /2)
9729		LeftHip:SetDesiredAngle(-3.14 /2)
9730	end
9731	
9732	local lastTick = 0
9733	
9734	function move(time)
9735		local amplitude = 1
9736		local frequency = 1
9737	  	local deltaTime = time - lastTick
9738	  	lastTick = time
9739	
9740		local climbFudge = 0
9741		local setAngles = false
9742	
9743	  	if (jumpAnimTime > 0) then
9744	  		jumpAnimTime = jumpAnimTime - deltaTime
9745	  	end
9746	
9747		if (pose == "FreeFall" and jumpAnimTime <= 0) then
9748			playAnimation("fall", fallTransitionTime, Humanoid)
9749		elseif (pose == "Seated") then
9750			playAnimation("sit", 0.5, Humanoid)
9751			return
9752		elseif (pose == "Running") then
9753			playAnimation("walk", 0.1, Humanoid)
9754		elseif (pose == "Dead" or pose == "GettingUp" or pose == "FallingDown" or pose == "Seated" or pose == "PlatformStanding") then
9755	--		print("Wha " .. pose)
9756			stopAllAnimations()
9757			amplitude = 0.1
9758			frequency = 1
9759			setAngles = true
9760		end
9761	
9762		if (setAngles) then
9763			local desiredAngle = amplitude * math.sin(time * frequency)
9764	
9765			RightShoulder:SetDesiredAngle(desiredAngle + climbFudge)
9766			LeftShoulder:SetDesiredAngle(desiredAngle - climbFudge)
9767			RightHip:SetDesiredAngle(-desiredAngle)
9768			LeftHip:SetDesiredAngle(-desiredAngle)
9769		end
9770	
9771		-- Tool Animation handling
9772		local tool = getTool()
9773		if tool and tool:FindFirstChild("Handle") then
9774		
9775			local animStringValueObject = getToolAnim(tool)
9776	
9777			if animStringValueObject then
9778				toolAnim = animStringValueObject.Value
9779				-- message recieved, delete StringValue
9780				animStringValueObject.Parent = nil
9781				toolAnimTime = time + .3
9782			end
9783	
9784			if time > toolAnimTime then
9785				toolAnimTime = 0
9786				toolAnim = "None"
9787			end
9788	
9789			animateTool()		
9790		else
9791			stopToolAnimations()
9792			toolAnim = "None"
9793			toolAnimInstance = nil
9794			toolAnimTime = 0
9795		end
9796	end
9797	
9798	
9799	local events = {}
9800	local eventHum = Humanoid
9801	
9802	local function onUnhook()
9803		for i = 1, #events do
9804			events[i]:Disconnect()
9805		end
9806		events = {}
9807	end
9808	
9809	local function onHook()
9810		onUnhook()
9811		
9812		pose = eventHum.Sit and "Seated" or "Standing"
9813		
9814		events = {
9815			eventHum.Died:connect(onDied),
9816			eventHum.Running:connect(onRunning),
9817			eventHum.Jumping:connect(onJumping),
9818			eventHum.Climbing:connect(onClimbing),
9819			eventHum.GettingUp:connect(onGettingUp),
9820			eventHum.FreeFalling:connect(onFreeFall),
9821			eventHum.FallingDown:connect(onFallingDown),
9822			eventHum.Seated:connect(onSeated),
9823			eventHum.PlatformStanding:connect(onPlatformStanding),
9824			eventHum.Swimming:connect(onSwimming)
9825		}
9826	end
9827	
9828	
9829	onHook()
9830	
9831	-- setup emote chat hook
9832	game:GetService("Players").LocalPlayer.Chatted:connect(function(msg)
9833		local emote = ""
9834		if msg == "/e dance" then
9835			emote = dances[math.random(1, #dances)]
9836		elseif (string.sub(msg, 1, 3) == "/e ") then
9837			emote = string.sub(msg, 4)
9838		elseif (string.sub(msg, 1, 7) == "/emote ") then
9839			emote = string.sub(msg, 8)
9840		end
9841		
9842		if (pose == "Standing" and emoteNames[emote] ~= nil) then
9843			playAnimation(emote, 0.1, Humanoid)
9844		end
9845	
9846	end)
9847	
9848	
9849	-- main program
9850	
9851	-- initialize to idle
9852	playAnimation("idle", 0.1, Humanoid)
9853	pose = "Standing"
9854	
9855	spawn(function()
9856		while Figure.Parent ~= nil do
9857			local _, time = wait(0.1)
9858			move(time)
9859		end
9860	end)
9861	
9862	return {
9863		onRunning = onRunning, 
9864		onDied = onDied, 
9865		onJumping = onJumping, 
9866		onClimbing = onClimbing, 
9867		onGettingUp = onGettingUp, 
9868		onFreeFall = onFreeFall, 
9869		onFallingDown = onFallingDown, 
9870		onSeated = onSeated, 
9871		onPlatformStanding = onPlatformStanding,
9872		onHook = onHook,
9873		onUnhook = onUnhook
9874	}
9875	
9876	end
9877	return r6()
9878end
9879
9880function _R15()
9881	local function r15()
9882		
9883	local Character = char
9884	local Humanoid = Character:WaitForChild("Humanoid")
9885	local pose = "Standing"
9886	
9887	local userNoUpdateOnLoopSuccess, userNoUpdateOnLoopValue = pcall(function() return UserSettings():IsUserFeatureEnabled("UserNoUpdateOnLoop") end)
9888	local userNoUpdateOnLoop = userNoUpdateOnLoopSuccess and userNoUpdateOnLoopValue
9889	local userAnimationSpeedDampeningSuccess, userAnimationSpeedDampeningValue = pcall(function() return UserSettings():IsUserFeatureEnabled("UserAnimationSpeedDampening") end)
9890	local userAnimationSpeedDampening = userAnimationSpeedDampeningSuccess and userAnimationSpeedDampeningValue
9891	
9892	local animateScriptEmoteHookFlagExists, animateScriptEmoteHookFlagEnabled = pcall(function()
9893		return UserSettings():IsUserFeatureEnabled("UserAnimateScriptEmoteHook")
9894	end)
9895	local FFlagAnimateScriptEmoteHook = animateScriptEmoteHookFlagExists and animateScriptEmoteHookFlagEnabled
9896	
9897	local AnimationSpeedDampeningObject = script:FindFirstChild("ScaleDampeningPercent")
9898	local HumanoidHipHeight = 2
9899	
9900	local EMOTE_TRANSITION_TIME = 0.1
9901	
9902	local currentAnim = ""
9903	local currentAnimInstance = nil
9904	local currentAnimTrack = nil
9905	local currentAnimKeyframeHandler = nil
9906	local currentAnimSpeed = 1.0
9907	
9908	local runAnimTrack = nil
9909	local runAnimKeyframeHandler = nil
9910	
9911	local animTable = {}
9912	local animNames = { 
9913		idle = 	{	
9914					{ id = "http://www.roblox.com/asset/?id=507766666", weight = 1 },
9915					{ id = "http://www.roblox.com/asset/?id=507766951", weight = 1 },
9916					{ id = "http://www.roblox.com/asset/?id=507766388", weight = 9 }
9917				},
9918		walk = 	{ 	
9919					{ id = "http://www.roblox.com/asset/?id=507777826", weight = 10 } 
9920				}, 
9921		run = 	{
9922					{ id = "http://www.roblox.com/asset/?id=507767714", weight = 10 } 
9923				}, 
9924		swim = 	{
9925					{ id = "http://www.roblox.com/asset/?id=507784897", weight = 10 } 
9926				}, 
9927		swimidle = 	{
9928					{ id = "http://www.roblox.com/asset/?id=507785072", weight = 10 } 
9929				}, 
9930		jump = 	{
9931					{ id = "http://www.roblox.com/asset/?id=507765000", weight = 10 } 
9932				}, 
9933		fall = 	{
9934					{ id = "http://www.roblox.com/asset/?id=507767968", weight = 10 } 
9935				}, 
9936		climb = {
9937					{ id = "http://www.roblox.com/asset/?id=507765644", weight = 10 } 
9938				}, 
9939		sit = 	{
9940					{ id = "http://www.roblox.com/asset/?id=2506281703", weight = 10 } 
9941				},	
9942		toolnone = {
9943					{ id = "http://www.roblox.com/asset/?id=507768375", weight = 10 } 
9944				},
9945		toolslash = {
9946					{ id = "http://www.roblox.com/asset/?id=522635514", weight = 10 } 
9947				},
9948		toollunge = {
9949					{ id = "http://www.roblox.com/asset/?id=522638767", weight = 10 } 
9950				},
9951		wave = {
9952					{ id = "http://www.roblox.com/asset/?id=507770239", weight = 10 } 
9953				},
9954		point = {
9955					{ id = "http://www.roblox.com/asset/?id=507770453", weight = 10 } 
9956				},
9957		dance = {
9958					{ id = "http://www.roblox.com/asset/?id=507771019", weight = 10 }, 
9959					{ id = "http://www.roblox.com/asset/?id=507771955", weight = 10 }, 
9960					{ id = "http://www.roblox.com/asset/?id=507772104", weight = 10 } 
9961				},
9962		dance2 = {
9963					{ id = "http://www.roblox.com/asset/?id=507776043", weight = 10 }, 
9964					{ id = "http://www.roblox.com/asset/?id=507776720", weight = 10 }, 
9965					{ id = "http://www.roblox.com/asset/?id=507776879", weight = 10 } 
9966				},
9967		dance3 = {
9968					{ id = "http://www.roblox.com/asset/?id=507777268", weight = 10 }, 
9969					{ id = "http://www.roblox.com/asset/?id=507777451", weight = 10 }, 
9970					{ id = "http://www.roblox.com/asset/?id=507777623", weight = 10 } 
9971				},
9972		laugh = {
9973					{ id = "http://www.roblox.com/asset/?id=507770818", weight = 10 } 
9974				},
9975		cheer = {
9976					{ id = "http://www.roblox.com/asset/?id=507770677", weight = 10 } 
9977				},
9978	}
9979	
9980	-- Existance in this list signifies that it is an emote, the value indicates if it is a looping emote
9981	local emoteNames = { wave = false, point = false, dance = true, dance2 = true, dance3 = true, laugh = false, cheer = false}
9982	
9983	local PreloadAnimsUserFlag = false
9984	local PreloadedAnims = {}
9985	local successPreloadAnim, msgPreloadAnim = pcall(function()
9986		PreloadAnimsUserFlag = UserSettings():IsUserFeatureEnabled("UserPreloadAnimations")
9987	end)
9988	if not successPreloadAnim then
9989		PreloadAnimsUserFlag = false
9990	end
9991	
9992	math.randomseed(tick())
9993	
9994	function findExistingAnimationInSet(set, anim)
9995		if set == nil or anim == nil then
9996			return 0
9997		end
9998		
9999		for idx = 1, set.count, 1 do 
10000			if set[idx].anim.AnimationId == anim.AnimationId then
10001				return idx
10002			end
10003		end
10004		
10005		return 0
10006	end
10007	
10008	function configureAnimationSet(name, fileList)
10009		if (animTable[name] ~= nil) then
10010			for _, connection in pairs(animTable[name].connections) do
10011				connection:disconnect()
10012			end
10013		end
10014		animTable[name] = {}
10015		animTable[name].count = 0
10016		animTable[name].totalWeight = 0	
10017		animTable[name].connections = {}
10018	
10019		local allowCustomAnimations = true
10020	
10021		local success, msg = pcall(function() allowCustomAnimations = game:GetService("StarterPlayer").AllowCustomAnimations end)
10022		if not success then
10023			allowCustomAnimations = true
10024		end
10025	
10026		-- check for config values
10027		local config = script:FindFirstChild(name)
10028		if (allowCustomAnimations and config ~= nil) then
10029			table.insert(animTable[name].connections, config.ChildAdded:connect(function(child) configureAnimationSet(name, fileList) end))
10030			table.insert(animTable[name].connections, config.ChildRemoved:connect(function(child) configureAnimationSet(name, fileList) end))
10031			
10032			local idx = 0
10033			for _, childPart in pairs(config:GetChildren()) do
10034				if (childPart:IsA("Animation")) then
10035					local newWeight = 1
10036					local weightObject = childPart:FindFirstChild("Weight")
10037					if (weightObject ~= nil) then
10038						newWeight = weightObject.Value
10039					end
10040					animTable[name].count = animTable[name].count + 1
10041					idx = animTable[name].count
10042					animTable[name][idx] = {}
10043					animTable[name][idx].anim = childPart
10044					animTable[name][idx].weight = newWeight
10045					animTable[name].totalWeight = animTable[name].totalWeight + animTable[name][idx].weight
10046					table.insert(animTable[name].connections, childPart.Changed:connect(function(property) configureAnimationSet(name, fileList) end))
10047					table.insert(animTable[name].connections, childPart.ChildAdded:connect(function(property) configureAnimationSet(name, fileList) end))
10048					table.insert(animTable[name].connections, childPart.ChildRemoved:connect(function(property) configureAnimationSet(name, fileList) end))
10049				end
10050			end
10051		end
10052		
10053		-- fallback to defaults
10054		if (animTable[name].count <= 0) then
10055			for idx, anim in pairs(fileList) do
10056				animTable[name][idx] = {}
10057				animTable[name][idx].anim = Instance.new("Animation")
10058				animTable[name][idx].anim.Name = name
10059				animTable[name][idx].anim.AnimationId = anim.id
10060				animTable[name][idx].weight = anim.weight
10061				animTable[name].count = animTable[name].count + 1
10062				animTable[name].totalWeight = animTable[name].totalWeight + anim.weight
10063			end
10064		end
10065		
10066		-- preload anims
10067		if PreloadAnimsUserFlag then
10068			for i, animType in pairs(animTable) do
10069				for idx = 1, animType.count, 1 do
10070					if PreloadedAnims[animType[idx].anim.AnimationId] == nil then
10071						Humanoid:LoadAnimation(animType[idx].anim)
10072						PreloadedAnims[animType[idx].anim.AnimationId] = true
10073					end				
10074				end
10075			end
10076		end
10077	end
10078	
10079	------------------------------------------------------------------------------------------------------------
10080	
10081	function configureAnimationSetOld(name, fileList)
10082		if (animTable[name] ~= nil) then
10083			for _, connection in pairs(animTable[name].connections) do
10084				connection:disconnect()
10085			end
10086		end
10087		animTable[name] = {}
10088		animTable[name].count = 0
10089		animTable[name].totalWeight = 0	
10090		animTable[name].connections = {}
10091	
10092		local allowCustomAnimations = true
10093	
10094		local success, msg = pcall(function() allowCustomAnimations = game:GetService("StarterPlayer").AllowCustomAnimations end)
10095		if not success then
10096			allowCustomAnimations = true
10097		end
10098	
10099		-- check for config values
10100		local config = script:FindFirstChild(name)
10101		if (allowCustomAnimations and config ~= nil) then
10102			table.insert(animTable[name].connections, config.ChildAdded:connect(function(child) configureAnimationSet(name, fileList) end))
10103			table.insert(animTable[name].connections, config.ChildRemoved:connect(function(child) configureAnimationSet(name, fileList) end))
10104			local idx = 1
10105			for _, childPart in pairs(config:GetChildren()) do
10106				if (childPart:IsA("Animation")) then
10107					table.insert(animTable[name].connections, childPart.Changed:connect(function(property) configureAnimationSet(name, fileList) end))
10108					animTable[name][idx] = {}
10109					animTable[name][idx].anim = childPart
10110					local weightObject = childPart:FindFirstChild("Weight")
10111					if (weightObject == nil) then
10112						animTable[name][idx].weight = 1
10113					else
10114						animTable[name][idx].weight = weightObject.Value
10115					end
10116					animTable[name].count = animTable[name].count + 1
10117					animTable[name].totalWeight = animTable[name].totalWeight + animTable[name][idx].weight
10118					idx = idx + 1
10119				end
10120			end
10121		end
10122	
10123		-- fallback to defaults
10124		if (animTable[name].count <= 0) then
10125			for idx, anim in pairs(fileList) do
10126				animTable[name][idx] = {}
10127				animTable[name][idx].anim = Instance.new("Animation")
10128				animTable[name][idx].anim.Name = name
10129				animTable[name][idx].anim.AnimationId = anim.id
10130				animTable[name][idx].weight = anim.weight
10131				animTable[name].count = animTable[name].count + 1
10132				animTable[name].totalWeight = animTable[name].totalWeight + anim.weight
10133				-- print(name .. " [" .. idx .. "] " .. anim.id .. " (" .. anim.weight .. ")")
10134			end
10135		end
10136		
10137		-- preload anims
10138		if PreloadAnimsUserFlag then
10139			for i, animType in pairs(animTable) do
10140				for idx = 1, animType.count, 1 do 
10141					Humanoid:LoadAnimation(animType[idx].anim)
10142				end
10143			end
10144		end
10145	end
10146	
10147	-- Setup animation objects
10148	function scriptChildModified(child)
10149		local fileList = animNames[child.Name]
10150		if (fileList ~= nil) then
10151			configureAnimationSet(child.Name, fileList)
10152		end	
10153	end
10154	
10155	script.ChildAdded:connect(scriptChildModified)
10156	script.ChildRemoved:connect(scriptChildModified)
10157	
10158	
10159	for name, fileList in pairs(animNames) do 
10160		configureAnimationSet(name, fileList)
10161	end	
10162	
10163	-- ANIMATION
10164	
10165	-- declarations
10166	local toolAnim = "None"
10167	local toolAnimTime = 0
10168	
10169	local jumpAnimTime = 0
10170	local jumpAnimDuration = 0.31
10171	
10172	local toolTransitionTime = 0.1
10173	local fallTransitionTime = 0.2
10174	
10175	local currentlyPlayingEmote = false
10176	
10177	-- functions
10178	
10179	function stopAllAnimations()
10180		local oldAnim = currentAnim
10181	
10182		-- return to idle if finishing an emote
10183		if (emoteNames[oldAnim] ~= nil and emoteNames[oldAnim] == false) then
10184			oldAnim = "idle"
10185		end
10186		
10187		if FFlagAnimateScriptEmoteHook and currentlyPlayingEmote then
10188			oldAnim = "idle"
10189			currentlyPlayingEmote = false
10190		end
10191	
10192		currentAnim = ""
10193		currentAnimInstance = nil
10194		if (currentAnimKeyframeHandler ~= nil) then
10195			currentAnimKeyframeHandler:disconnect()
10196		end
10197	
10198		if (currentAnimTrack ~= nil) then
10199			currentAnimTrack:Stop()
10200			currentAnimTrack:Destroy()
10201			currentAnimTrack = nil
10202		end
10203	
10204		-- clean up walk if there is one
10205		if (runAnimKeyframeHandler ~= nil) then
10206			runAnimKeyframeHandler:disconnect()
10207		end
10208		
10209		if (runAnimTrack ~= nil) then
10210			runAnimTrack:Stop()
10211			runAnimTrack:Destroy()
10212			runAnimTrack = nil
10213		end
10214		
10215		return oldAnim
10216	end
10217	
10218	function getHeightScale()
10219		if Humanoid then
10220			if not Humanoid.AutomaticScalingEnabled then
10221				return 1
10222			end
10223			
10224			local scale = Humanoid.HipHeight / HumanoidHipHeight
10225			if userAnimationSpeedDampening then
10226				if AnimationSpeedDampeningObject == nil then
10227					AnimationSpeedDampeningObject = script:FindFirstChild("ScaleDampeningPercent")
10228				end
10229				if AnimationSpeedDampeningObject ~= nil then
10230					scale = 1 + (Humanoid.HipHeight - HumanoidHipHeight) * AnimationSpeedDampeningObject.Value / HumanoidHipHeight
10231				end
10232			end
10233			return scale
10234		end	
10235		return 1
10236	end
10237	
10238	local smallButNotZero = 0.0001
10239	function setRunSpeed(speed)
10240		local speedScaled = speed * 1.25
10241		local heightScale = getHeightScale()
10242		local runSpeed = speedScaled / heightScale
10243	
10244		if runSpeed ~= currentAnimSpeed then
10245			if runSpeed < 0.33 then
10246				currentAnimTrack:AdjustWeight(1.0)		
10247				runAnimTrack:AdjustWeight(smallButNotZero)
10248			elseif runSpeed < 0.66 then
10249				local weight = ((runSpeed - 0.33) / 0.33)
10250				currentAnimTrack:AdjustWeight(1.0 - weight + smallButNotZero)
10251				runAnimTrack:AdjustWeight(weight + smallButNotZero)
10252			else
10253				currentAnimTrack:AdjustWeight(smallButNotZero)
10254				runAnimTrack:AdjustWeight(1.0)
10255			end
10256			currentAnimSpeed = runSpeed
10257			runAnimTrack:AdjustSpeed(runSpeed)
10258			currentAnimTrack:AdjustSpeed(runSpeed)
10259		end	
10260	end
10261	
10262	function setAnimationSpeed(speed)
10263		if currentAnim == "walk" then
10264				setRunSpeed(speed)
10265		else
10266			if speed ~= currentAnimSpeed then
10267				currentAnimSpeed = speed
10268				currentAnimTrack:AdjustSpeed(currentAnimSpeed)
10269			end
10270		end
10271	end
10272	
10273	function keyFrameReachedFunc(frameName)
10274		if (frameName == "End") then
10275			if currentAnim == "walk" then
10276				if userNoUpdateOnLoop == true then
10277					if runAnimTrack.Looped ~= true then
10278						runAnimTrack.TimePosition = 0.0
10279					end
10280					if currentAnimTrack.Looped ~= true then
10281						currentAnimTrack.TimePosition = 0.0
10282					end
10283				else
10284					runAnimTrack.TimePosition = 0.0
10285					currentAnimTrack.TimePosition = 0.0
10286				end
10287			else
10288				local repeatAnim = currentAnim
10289				-- return to idle if finishing an emote
10290				if (emoteNames[repeatAnim] ~= nil and emoteNames[repeatAnim] == false) then
10291					repeatAnim = "idle"
10292				end
10293				
10294				if FFlagAnimateScriptEmoteHook and currentlyPlayingEmote then
10295					if currentAnimTrack.Looped then
10296						-- Allow the emote to loop
10297						return
10298					end
10299					
10300					repeatAnim = "idle"
10301					currentlyPlayingEmote = false
10302				end
10303				
10304				local animSpeed = currentAnimSpeed
10305				playAnimation(repeatAnim, 0.15, Humanoid)
10306				setAnimationSpeed(animSpeed)
10307			end
10308		end
10309	end
10310	
10311	function rollAnimation(animName)
10312		local roll = math.random(1, animTable[animName].totalWeight) 
10313		local origRoll = roll
10314		local idx = 1
10315		while (roll > animTable[animName][idx].weight) do
10316			roll = roll - animTable[animName][idx].weight
10317			idx = idx + 1
10318		end
10319		return idx
10320	end
10321	
10322	local function switchToAnim(anim, animName, transitionTime, humanoid)
10323		-- switch animation		
10324		if (anim ~= currentAnimInstance) then
10325			
10326			if (currentAnimTrack ~= nil) then
10327				currentAnimTrack:Stop(transitionTime)
10328				currentAnimTrack:Destroy()
10329			end
10330	
10331			if (runAnimTrack ~= nil) then
10332				runAnimTrack:Stop(transitionTime)
10333				runAnimTrack:Destroy()
10334				if userNoUpdateOnLoop == true then
10335					runAnimTrack = nil
10336				end
10337			end
10338	
10339			currentAnimSpeed = 1.0
10340		
10341			-- load it to the humanoid; get AnimationTrack
10342			currentAnimTrack = humanoid:LoadAnimation(anim)
10343			currentAnimTrack.Priority = Enum.AnimationPriority.Core
10344			 
10345			-- play the animation
10346			currentAnimTrack:Play(transitionTime)
10347			currentAnim = animName
10348			currentAnimInstance = anim
10349	
10350			-- set up keyframe name triggers
10351			if (currentAnimKeyframeHandler ~= nil) then
10352				currentAnimKeyframeHandler:disconnect()
10353			end
10354			currentAnimKeyframeHandler = currentAnimTrack.KeyframeReached:connect(keyFrameReachedFunc)
10355			
10356			-- check to see if we need to blend a walk/run animation
10357			if animName == "walk" then
10358				local runAnimName = "run"
10359				local runIdx = rollAnimation(runAnimName)
10360	
10361				runAnimTrack = humanoid:LoadAnimation(animTable[runAnimName][runIdx].anim)
10362				runAnimTrack.Priority = Enum.AnimationPriority.Core
10363				runAnimTrack:Play(transitionTime)		
10364				
10365				if (runAnimKeyframeHandler ~= nil) then
10366					runAnimKeyframeHandler:disconnect()
10367				end
10368				runAnimKeyframeHandler = runAnimTrack.KeyframeReached:connect(keyFrameReachedFunc)	
10369			end
10370		end
10371	end
10372	
10373	function playAnimation(animName, transitionTime, humanoid) 	
10374		local idx = rollAnimation(animName)
10375		local anim = animTable[animName][idx].anim
10376	
10377		switchToAnim(anim, animName, transitionTime, humanoid)
10378		currentlyPlayingEmote = false
10379	end
10380	
10381	function playEmote(emoteAnim, transitionTime, humanoid)
10382		switchToAnim(emoteAnim, emoteAnim.Name, transitionTime, humanoid)
10383		currentlyPlayingEmote = true
10384	end
10385	
10386	-------------------------------------------------------------------------------------------
10387	-------------------------------------------------------------------------------------------
10388	
10389	local toolAnimName = ""
10390	local toolAnimTrack = nil
10391	local toolAnimInstance = nil
10392	local currentToolAnimKeyframeHandler = nil
10393	
10394	function toolKeyFrameReachedFunc(frameName)
10395		if (frameName == "End") then
10396			playToolAnimation(toolAnimName, 0.0, Humanoid)
10397		end
10398	end
10399	
10400	
10401	function playToolAnimation(animName, transitionTime, humanoid, priority)	 		
10402			local idx = rollAnimation(animName)
10403			local anim = animTable[animName][idx].anim
10404	
10405			if (toolAnimInstance ~= anim) then
10406				
10407				if (toolAnimTrack ~= nil) then
10408					toolAnimTrack:Stop()
10409					toolAnimTrack:Destroy()
10410					transitionTime = 0
10411				end
10412						
10413				-- load it to the humanoid; get AnimationTrack
10414				toolAnimTrack = humanoid:LoadAnimation(anim)
10415				if priority then
10416					toolAnimTrack.Priority = priority
10417				end
10418				 
10419				-- play the animation
10420				toolAnimTrack:Play(transitionTime)
10421				toolAnimName = animName
10422				toolAnimInstance = anim
10423	
10424				currentToolAnimKeyframeHandler = toolAnimTrack.KeyframeReached:connect(toolKeyFrameReachedFunc)
10425			end
10426	end
10427	
10428	function stopToolAnimations()
10429		local oldAnim = toolAnimName
10430	
10431		if (currentToolAnimKeyframeHandler ~= nil) then
10432			currentToolAnimKeyframeHandler:disconnect()
10433		end
10434	
10435		toolAnimName = ""
10436		toolAnimInstance = nil
10437		if (toolAnimTrack ~= nil) then
10438			toolAnimTrack:Stop()
10439			toolAnimTrack:Destroy()
10440			toolAnimTrack = nil
10441		end
10442	
10443		return oldAnim
10444	end
10445	
10446	-------------------------------------------------------------------------------------------
10447	-------------------------------------------------------------------------------------------
10448	-- STATE CHANGE HANDLERS
10449	
10450	function onRunning(speed)
10451		if speed > 0.75 then
10452			local scale = 16.0
10453			playAnimation("walk", 0.2, Humanoid)
10454			setAnimationSpeed(speed / scale)
10455			pose = "Running"
10456		else
10457			if emoteNames[currentAnim] == nil and not currentlyPlayingEmote then
10458				playAnimation("idle", 0.2, Humanoid)
10459				pose = "Standing"
10460			end
10461		end
10462	end
10463	
10464	function onDied()
10465		pose = "Dead"
10466	end
10467	
10468	function onJumping()
10469		playAnimation("jump", 0.1, Humanoid)
10470		jumpAnimTime = jumpAnimDuration
10471		pose = "Jumping"
10472	end
10473	
10474	function onClimbing(speed)
10475		local scale = 5.0
10476		playAnimation("climb", 0.1, Humanoid)
10477		setAnimationSpeed(speed / scale)
10478		pose = "Climbing"
10479	end
10480	
10481	function onGettingUp()
10482		pose = "GettingUp"
10483	end
10484	
10485	function onFreeFall()
10486		if (jumpAnimTime <= 0) then
10487			playAnimation("fall", fallTransitionTime, Humanoid)
10488		end
10489		pose = "FreeFall"
10490	end
10491	
10492	function onFallingDown()
10493		pose = "FallingDown"
10494	end
10495	
10496	function onSeated()
10497		pose = "Seated"
10498	end
10499	
10500	function onPlatformStanding()
10501		pose = "PlatformStanding"
10502	end
10503	
10504	-------------------------------------------------------------------------------------------
10505	-------------------------------------------------------------------------------------------
10506	
10507	function onSwimming(speed)
10508		if speed > 1.00 then
10509			local scale = 10.0
10510			playAnimation("swim", 0.4, Humanoid)
10511			setAnimationSpeed(speed / scale)
10512			pose = "Swimming"
10513		else
10514			playAnimation("swimidle", 0.4, Humanoid)
10515			pose = "Standing"
10516		end
10517	end
10518	
10519	function animateTool()
10520		if (toolAnim == "None") then
10521			playToolAnimation("toolnone", toolTransitionTime, Humanoid, Enum.AnimationPriority.Idle)
10522			return
10523		end
10524	
10525		if (toolAnim == "Slash") then
10526			playToolAnimation("toolslash", 0, Humanoid, Enum.AnimationPriority.Action)
10527			return
10528		end
10529	
10530		if (toolAnim == "Lunge") then
10531			playToolAnimation("toollunge", 0, Humanoid, Enum.AnimationPriority.Action)
10532			return
10533		end
10534	end
10535	
10536	function getToolAnim(tool)
10537		for _, c in ipairs(tool:GetChildren()) do
10538			if c.Name == "toolanim" and c.className == "StringValue" then
10539				return c
10540			end
10541		end
10542		return nil
10543	end
10544	
10545	local lastTick = 0
10546	
10547	function stepAnimate(currentTime)
10548		local amplitude = 1
10549		local frequency = 1
10550	  	local deltaTime = currentTime - lastTick
10551	  	lastTick = currentTime
10552	
10553		local climbFudge = 0
10554		local setAngles = false
10555	
10556	  	if (jumpAnimTime > 0) then
10557	  		jumpAnimTime = jumpAnimTime - deltaTime
10558	  	end
10559	
10560		if (pose == "FreeFall" and jumpAnimTime <= 0) then
10561			playAnimation("fall", fallTransitionTime, Humanoid)
10562		elseif (pose == "Seated") then
10563			playAnimation("sit", 0.5, Humanoid)
10564			return
10565		elseif (pose == "Running") then
10566			playAnimation("walk", 0.2, Humanoid)
10567		elseif (pose == "Dead" or pose == "GettingUp" or pose == "FallingDown" or pose == "Seated" or pose == "PlatformStanding") then
10568			stopAllAnimations()
10569			amplitude = 0.1
10570			frequency = 1
10571			setAngles = true
10572		end
10573	
10574		-- Tool Animation handling
10575		local tool = Character:FindFirstChildOfClass("Tool")
10576		if tool and tool:FindFirstChild("Handle") then
10577			local animStringValueObject = getToolAnim(tool)
10578	
10579			if animStringValueObject then
10580				toolAnim = animStringValueObject.Value
10581				-- message recieved, delete StringValue
10582				animStringValueObject.Parent = nil
10583				toolAnimTime = currentTime + .3
10584			end
10585	
10586			if currentTime > toolAnimTime then
10587				toolAnimTime = 0
10588				toolAnim = "None"
10589			end
10590	
10591			animateTool()		
10592		else
10593			stopToolAnimations()
10594			toolAnim = "None"
10595			toolAnimInstance = nil
10596			toolAnimTime = 0
10597		end
10598	end
10599	
10600	-- connect events
10601	
10602	local events = {}
10603	local eventHum = Humanoid
10604	
10605	local function onUnhook()
10606		for i = 1, #events do
10607			events[i]:Disconnect()
10608		end
10609		events = {}
10610	end
10611	
10612	local function onHook()
10613		onUnhook()
10614		
10615		pose = eventHum.Sit and "Seated" or "Standing"
10616		
10617		events = {
10618			eventHum.Died:connect(onDied),
10619			eventHum.Running:connect(onRunning),
10620			eventHum.Jumping:connect(onJumping),
10621			eventHum.Climbing:connect(onClimbing),
10622			eventHum.GettingUp:connect(onGettingUp),
10623			eventHum.FreeFalling:connect(onFreeFall),
10624			eventHum.FallingDown:connect(onFallingDown),
10625			eventHum.Seated:connect(onSeated),
10626			eventHum.PlatformStanding:connect(onPlatformStanding),
10627			eventHum.Swimming:connect(onSwimming)
10628		}
10629	end
10630	
10631	
10632	onHook()
10633	
10634	-- setup emote chat hook
10635	game:GetService("Players").LocalPlayer.Chatted:connect(function(msg)
10636		local emote = ""
10637		if (string.sub(msg, 1, 3) == "/e ") then
10638			emote = string.sub(msg, 4)
10639		elseif (string.sub(msg, 1, 7) == "/emote ") then
10640			emote = string.sub(msg, 8)
10641		end
10642		
10643		if (pose == "Standing" and emoteNames[emote] ~= nil) then
10644			playAnimation(emote, EMOTE_TRANSITION_TIME, Humanoid)
10645		end
10646	end)
10647	
10648	--[[ emote bindable hook
10649	if FFlagAnimateScriptEmoteHook then
10650		script:WaitForChild("PlayEmote").OnInvoke = function(emote)
10651			-- Only play emotes when idling
10652			if pose ~= "Standing" then
10653				return
10654			end
10655			if emoteNames[emote] ~= nil then
10656				-- Default emotes
10657				playAnimation(emote, EMOTE_TRANSITION_TIME, Humanoid)
10658				return true
10659			elseif typeof(emote) == "Instance" and emote:IsA("Animation") then
10660				-- Non-default emotes
10661				playEmote(emote, EMOTE_TRANSITION_TIME, Humanoid)
10662				return true
10663			end
10664			-- Return false to indicate that the emote could not be played
10665			return false
10666		end
10667	end
10668	]]
10669	-- initialize to idle
10670	playAnimation("idle", 0.1, Humanoid)
10671	pose = "Standing"
10672	-- loop to handle timed state transitions and tool animations
10673	spawn(function()
10674		while Character.Parent ~= nil do
10675			local _, currentGameTime = wait(0.1)
10676			stepAnimate(currentGameTime)
10677		end
10678	end)
10679	return {
10680		onRunning = onRunning, 
10681		onDied = onDied, 
10682		onJumping = onJumping, 
10683		onClimbing = onClimbing, 
10684		onGettingUp = onGettingUp, 
10685		onFreeFall = onFreeFall, 
10686		onFallingDown = onFallingDown, 
10687		onSeated = onSeated, 
10688		onPlatformStanding = onPlatformStanding,
10689		onHook = onHook,
10690		onUnhook = onUnhook
10691	}
10692	end
10693	return r15()
10694end
10695while true do
10696	wait(.1)
10697	if plr.Character ~= nil then
10698		char = plr.Character
10699		break
10700	end
10701end
10702function _Controller()
10703	local humanoid = char:WaitForChild("Humanoid")
10704	local animFuncs = {}
10705	if (humanoid.RigType == Enum.HumanoidRigType.R6) then
10706		animFuncs = _R6()
10707	else
10708		animFuncs = _R15()
10709	end
10710	print("Animation succes")
10711	return animFuncs
10712end
10713function _AnimationHandler()
10714local AnimationHandler = {}
10715AnimationHandler.__index = AnimationHandler
10716
10717function AnimationHandler.new(humanoid, animate)
10718	local self = setmetatable({}, AnimationHandler)
10719	
10720	self._AnimFuncs = _Controller()
10721	self.Humanoid = humanoid
10722	
10723	return self
10724end
10725
10726function AnimationHandler:EnableDefault(bool)
10727	if (bool) then
10728		self._AnimFuncs.onHook()
10729	else
10730		self._AnimFuncs.onUnhook()
10731	end
10732end
10733
10734function AnimationHandler:Run(name, ...)
10735	self._AnimFuncs[name](...)
10736end
10737
10738return AnimationHandler
10739end
10740
10741function _GravityController()
10742
10743local ZERO = Vector3.new(0, 0, 0)
10744local UNIT_X = Vector3.new(1, 0, 0)
10745local UNIT_Y = Vector3.new(0, 1, 0)
10746local UNIT_Z = Vector3.new(0, 0, 1)
10747local VEC_XY = Vector3.new(1, 0, 1)
10748
10749local IDENTITYCF = CFrame.new()
10750
10751local JUMPMODIFIER = 1.2
10752local TRANSITION = 0.15
10753local WALKF = 200 / 3
10754
10755local UIS = game:GetService("UserInputService")
10756local RUNSERVICE = game:GetService("RunService")
10757
10758local InitObjects = _InitObjects()
10759local AnimationHandler = _AnimationHandler()
10760local StateTracker = _StateTracker()
10761
10762-- Class
10763
10764local GravityController = {}
10765GravityController.__index = GravityController
10766
10767-- Private Functions
10768
10769local function getRotationBetween(u, v, axis)
10770	local dot, uxv = u:Dot(v), u:Cross(v)
10771	if (dot < -0.99999) then return CFrame.fromAxisAngle(axis, math.pi) end
10772	return CFrame.new(0, 0, 0, uxv.x, uxv.y, uxv.z, 1 + dot)
10773end
10774
10775local function lookAt(pos, forward, up)
10776	local r = forward:Cross(up)
10777	local u = r:Cross(forward)
10778	return CFrame.fromMatrix(pos, r.Unit, u.Unit)
10779end
10780
10781local function getMass(array)
10782	local mass = 0
10783	for _, part in next, array do
10784		if (part:IsA("BasePart")) then
10785			mass = mass + part:GetMass()
10786		end
10787	end
10788	return mass
10789end
10790
10791-- Public Constructor
10792local ExecutedPlayerModule = _PlayerModule()
10793local ExecutedSounds = _sounds()
10794function GravityController.new(player)
10795	local self = setmetatable({}, GravityController)
10796
10797	--[[ Camera
10798	local loaded = player.PlayerScripts:WaitForChild("PlayerScriptsLoader"):WaitForChild("Loaded")
10799	if (not loaded.Value) then
10800		--loaded.Changed:Wait()
10801	end
10802	]]
10803	local playerModule = ExecutedPlayerModule
10804	self.Controls = playerModule:GetControls()
10805	self.Camera = playerModule:GetCameras()
10806	
10807	-- Player and character
10808	self.Player = player
10809	self.Character = player.Character
10810	self.Humanoid = player.Character:WaitForChild("Humanoid")
10811	self.HRP = player.Character:WaitForChild("HumanoidRootPart")
10812	
10813	-- Animation
10814	self.AnimationHandler = AnimationHandler.new(self.Humanoid, self.Character:WaitForChild("Animate"))
10815	self.AnimationHandler:EnableDefault(false)
10816	local ssss = game:GetService("Players").LocalPlayer.PlayerScripts:FindFirstChild("SetState") or Instance.new("BindableEvent",game:GetService("Players").LocalPlayer.PlayerScripts)
10817	local soundState = ExecutedSounds
10818	ssss.Name = "SetState"
10819	
10820	self.StateTracker = StateTracker.new(self.Humanoid, soundState)
10821	self.StateTracker.Changed:Connect(function(name, speed)
10822		self.AnimationHandler:Run(name, speed)
10823	end)
10824	
10825	-- Collider and forces
10826	local collider, gyro, vForce, floor = InitObjects(self)
10827	
10828	floor.Touched:Connect(function() end)
10829	collider.Touched:Connect(function() end)
10830	
10831	self.Collider = collider
10832	self.VForce = vForce
10833	self.Gyro = gyro
10834	self.Floor = floor
10835	
10836	-- Attachment to parts
10837	self.LastPart = workspace.Terrain
10838	self.LastPartCFrame = IDENTITYCF
10839	
10840	-- Gravity properties
10841	self.GravityUp = UNIT_Y
10842	self.Ignores = {self.Character}
10843	
10844	function self.Camera.GetUpVector(this, oldUpVector)
10845		return self.GravityUp
10846	end
10847	
10848	-- Events etc
10849	self.Humanoid.PlatformStand = true
10850	
10851	self.CharacterMass = getMass(self.Character:GetDescendants())
10852	self.Character.AncestryChanged:Connect(function() self.CharacterMass = getMass(self.Character:GetDescendants()) end)
10853	
10854	self.JumpCon = RUNSERVICE.RenderStepped:Connect(function(dt) 
10855		if (self.Controls:IsJumping()) then
10856			self:OnJumpRequest()
10857		end
10858	end)
10859	
10860	self.DeathCon = self.Humanoid.Died:Connect(function() self:Destroy() end)
10861	self.SeatCon = self.Humanoid.Seated:Connect(function(active) if (active) then self:Destroy() end end)
10862	self.HeartCon = RUNSERVICE.Heartbeat:Connect(function(dt) self:OnHeartbeatStep(dt) end)
10863	RUNSERVICE:BindToRenderStep("GravityStep", Enum.RenderPriority.Input.Value + 1, function(dt) self:OnGravityStep(dt) end)
10864	
10865	
10866	return self
10867end
10868
10869-- Public Methods
10870
10871function GravityController:Destroy()
10872	self.JumpCon:Disconnect()
10873	self.DeathCon:Disconnect()
10874	self.SeatCon:Disconnect()
10875	self.HeartCon:Disconnect()
10876	
10877	RUNSERVICE:UnbindFromRenderStep("GravityStep")
10878	
10879	self.Collider:Destroy()
10880	self.VForce:Destroy()
10881	self.Gyro:Destroy()
10882	self.StateTracker:Destroy()
10883	
10884	self.Humanoid.PlatformStand = false
10885	self.AnimationHandler:EnableDefault(true)
10886	
10887	self.GravityUp = UNIT_Y
10888end
10889
10890function GravityController:GetGravityUp(oldGravity)
10891	return oldGravity
10892end
10893
10894function GravityController:IsGrounded(isJumpCheck)
10895	if (not isJumpCheck) then
10896		local parts = self.Floor:GetTouchingParts()
10897		for _, part in next, parts do
10898			if (not part:IsDescendantOf(self.Character)) then
10899				return true
10900			end
10901		end
10902	else
10903		if (self.StateTracker.Jumped) then
10904			return false
10905		end
10906	
10907		-- 1. check we are touching something with the collider
10908		local valid = {}
10909		local parts = self.Collider:GetTouchingParts()
10910		for _, part in next, parts do
10911			if (not part:IsDescendantOf(self.Character)) then
10912				table.insert(valid, part)
10913			end
10914		end
10915		
10916		if (#valid > 0) then
10917			-- 2. do a decently long downwards raycast
10918			local max = math.cos(self.Humanoid.MaxSlopeAngle)
10919			local ray = Ray.new(self.Collider.Position, -10 * self.GravityUp)
10920			local hit, pos, normal = workspace:FindPartOnRayWithWhitelist(ray, valid, true)
10921			
10922			-- 3. use slope to decide on jump
10923			if (hit and max <= self.GravityUp:Dot(normal)) then
10924				return true
10925			end
10926		end
10927	end
10928	return false
10929end
10930
10931function GravityController:OnJumpRequest()
10932	if (not self.StateTracker.Jumped and self:IsGrounded(true)) then
10933		local hrpVel = self.HRP.Velocity
10934		self.HRP.Velocity = hrpVel + self.GravityUp*self.Humanoid.JumpPower*JUMPMODIFIER
10935		self.StateTracker:RequestedJump()
10936	end
10937end
10938
10939function GravityController:GetMoveVector()
10940	return self.Controls:GetMoveVector()
10941end
10942
10943function GravityController:OnHeartbeatStep(dt)
10944	local ray = Ray.new(self.Collider.Position, -1.1*self.GravityUp)
10945	local hit, pos, normal = workspace:FindPartOnRayWithIgnoreList(ray, self.Ignores)
10946	local lastPart = self.LastPart
10947	
10948	if (hit and lastPart and lastPart == hit) then
10949		local offset = self.LastPartCFrame:ToObjectSpace(self.HRP.CFrame)
10950		self.HRP.CFrame = hit.CFrame:ToWorldSpace(offset)
10951	end
10952	
10953	self.LastPart = hit
10954	self.LastPartCFrame = hit and hit.CFrame
10955end
10956
10957function GravityController:OnGravityStep(dt)
10958	-- update gravity up vector
10959	local oldGravity = self.GravityUp
10960	local newGravity = self:GetGravityUp(oldGravity)
10961	
10962	local rotation = getRotationBetween(oldGravity, newGravity, workspace.CurrentCamera.CFrame.RightVector)
10963	rotation = IDENTITYCF:Lerp(rotation, TRANSITION)
10964	
10965	self.GravityUp = rotation * oldGravity
10966	
10967	-- get world move vector
10968	local camCF = workspace.CurrentCamera.CFrame
10969	local fDot = camCF.LookVector:Dot(newGravity)
10970	local cForward = math.abs(fDot) > 0.5 and -math.sign(fDot)*camCF.UpVector or camCF.LookVector
10971	
10972	local left = cForward:Cross(-newGravity).Unit
10973	local forward = -left:Cross(newGravity).Unit
10974	
10975	local move = self:GetMoveVector()
10976	local worldMove = forward*move.z - left*move.x
10977	worldMove = worldMove:Dot(worldMove) > 1 and worldMove.Unit or worldMove
10978	
10979	local isInputMoving = worldMove:Dot(worldMove) > 0
10980	
10981	-- get the desired character cframe
10982	local hrpCFLook = self.HRP.CFrame.LookVector
10983	local charF = hrpCFLook:Dot(forward)*forward + hrpCFLook:Dot(left)*left
10984	local charR = charF:Cross(newGravity).Unit
10985	local newCharCF = CFrame.fromMatrix(ZERO, charR, newGravity, -charF)
10986	
10987	local newCharRotation = IDENTITYCF
10988	if (isInputMoving) then
10989		newCharRotation = IDENTITYCF:Lerp(getRotationBetween(charF, worldMove, newGravity), 0.7)	
10990	end
10991	
10992	-- calculate forces
10993	local g = workspace.Gravity
10994	local gForce = g * self.CharacterMass * (UNIT_Y - newGravity)
10995	
10996	local cVelocity = self.HRP.Velocity
10997	local tVelocity = self.Humanoid.WalkSpeed * worldMove
10998	local gVelocity = cVelocity:Dot(newGravity)*newGravity
10999	local hVelocity = cVelocity - gVelocity
11000	
11001	if (hVelocity:Dot(hVelocity) < 1) then
11002		hVelocity = ZERO
11003	end
11004	
11005	local dVelocity = tVelocity - hVelocity
11006	local walkForceM = math.min(10000, WALKF * self.CharacterMass * dVelocity.Magnitude / (dt*60))
11007	local walkForce = walkForceM > 0 and dVelocity.Unit*walkForceM or ZERO
11008	
11009	-- mouse lock
11010	local charRotation = newCharRotation * newCharCF
11011	
11012	if (self.Camera:IsCamRelative()) then
11013		local lv = workspace.CurrentCamera.CFrame.LookVector
11014		local hlv = lv - charRotation.UpVector:Dot(lv)*charRotation.UpVector
11015		charRotation = lookAt(ZERO, hlv, charRotation.UpVector)
11016	end
11017	
11018	-- get state
11019	self.StateTracker:OnStep(self.GravityUp, self:IsGrounded(), isInputMoving)
11020
11021	-- update values
11022	self.VForce.Force = walkForce + gForce
11023	self.Gyro.CFrame = charRotation
11024end
11025return GravityController
11026end
11027function _Draw3D()
11028	local module = {}
11029	
11030	-- Style Guide
11031	
11032	module.StyleGuide = {
11033		Point = {
11034			Thickness = 0.5;
11035			Color = Color3.new(0, 1, 0);
11036		},
11037		
11038		Line = {
11039			Thickness = 0.1;
11040			Color = Color3.new(1, 1, 0);
11041		},
11042		
11043		Ray = {
11044			Thickness = 0.1;
11045			Color = Color3.new(1, 0, 1);
11046		},
11047		
11048		Triangle = {
11049			Thickness = 0.05;
11050		};
11051		
11052		CFrame = {
11053			Thickness = 0.1;
11054			RightColor3 = Color3.new(1, 0, 0);
11055			UpColor3 = Color3.new(0, 1, 0);
11056			BackColor3 = Color3.new(0, 0, 1);
11057			PartProperties = {
11058				Material = Enum.Material.SmoothPlastic;
11059			};
11060		}
11061	}
11062	
11063	-- CONSTANTS
11064	
11065	local WEDGE = Instance.new("WedgePart")
11066	WEDGE.Material = Enum.Material.SmoothPlastic
11067	WEDGE.Anchored = true
11068	WEDGE.CanCollide = false
11069	
11070	local PART = Instance.new("Part")
11071	PART.Size = Vector3.new(0.1, 0.1, 0.1)
11072	PART.Anchored = true
11073	PART.CanCollide = false
11074	PART.TopSurface = Enum.SurfaceType.Smooth
11075	PART.BottomSurface = Enum.SurfaceType.Smooth
11076	PART.Material = Enum.Material.SmoothPlastic
11077	
11078	-- Functions
11079	
11080	local function draw(properties, style)
11081		local part = PART:Clone()
11082		for k, v in next, properties do
11083			part[k] = v
11084		end
11085		if (style) then
11086			for k, v in next, style do
11087				if (k ~= "Thickness") then
11088					part[k] = v
11089				end
11090			end
11091		end
11092		return part
11093	end
11094	
11095	function module.Draw(parent, properties)
11096		properties.Parent = parent
11097		return draw(properties, nil)
11098	end
11099	
11100	function module.Point(parent, cf_v3)
11101		local thickness = module.StyleGuide.Point.Thickness
11102		return draw({
11103			Size = Vector3.new(thickness, thickness, thickness);
11104			CFrame = (typeof(cf_v3) == "CFrame" and cf_v3 or CFrame.new(cf_v3));
11105			Parent = parent;
11106		}, module.StyleGuide.Point)
11107	end
11108	
11109	function module.Line(parent, a, b)
11110		local thickness = module.StyleGuide.Line.Thickness
11111		return draw({
11112			CFrame = CFrame.new((a + b)/2, b);
11113			Size = Vector3.new(thickness, thickness, (b - a).Magnitude);
11114			Parent = parent;
11115		}, module.StyleGuide.Line)
11116	end
11117	
11118	function module.Ray(parent, origin, direction)
11119		local thickness = module.StyleGuide.Ray.Thickness
11120		return draw({
11121			CFrame = CFrame.new(origin + direction/2, origin + direction);
11122			Size = Vector3.new(thickness, thickness, direction.Magnitude);
11123			Parent = parent;
11124		}, module.StyleGuide.Ray)
11125	end
11126	
11127	function module.Triangle(parent, a, b, c)
11128		local ab, ac, bc = b - a, c - a, c - b
11129		local abd, acd, bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc)
11130		
11131		if (abd > acd and abd > bcd) then
11132			c, a = a, c
11133		elseif (acd > bcd and acd > abd) then
11134			a, b = b, a
11135		end
11136		
11137		ab, ac, bc = b - a, c - a, c - b
11138		
11139		local right = ac:Cross(ab).Unit
11140		local up = bc:Cross(right).Unit
11141		local back = bc.Unit
11142		
11143		local height = math.abs(ab:Dot(up))
11144		local width1 = math.abs(ab:Dot(back))
11145		local width2 = math.abs(ac:Dot(back))
11146		
11147		local thickness = module.StyleGuide.Triangle.Thickness
11148		
11149		local w1 = WEDGE:Clone()
11150		w1.Size = Vector3.new(thickness, height, width1)
11151		w1.CFrame = CFrame.fromMatrix((a + b)/2, right, up, back)
11152		w1.Parent = parent
11153		
11154		local w2 = WEDGE:Clone()
11155		w2.Size = Vector3.new(thickness, height, width2)
11156		w2.CFrame = CFrame.fromMatrix((a + c)/2, -right, up, -back)
11157		w2.Parent = parent
11158		
11159		for k, v in next, module.StyleGuide.Triangle do
11160			if (k ~= "Thickness") then
11161				w1[k] = v
11162				w2[k] = v
11163			end
11164		end
11165		
11166		return w1, w2
11167	end
11168	
11169	function module.CFrame(parent, cf)
11170		local origin = cf.Position
11171		local r = cf.RightVector
11172		local u = cf.UpVector
11173		local b = -cf.LookVector
11174		
11175		local thickness = module.StyleGuide.CFrame.Thickness
11176		
11177		local right = draw({
11178			CFrame = CFrame.new(origin + r/2, origin + r);
11179			Size = Vector3.new(thickness, thickness, r.Magnitude);
11180			Color = module.StyleGuide.CFrame.RightColor3;
11181			Parent = parent;
11182		}, module.StyleGuide.CFrame.PartProperties)
11183		
11184		local up = draw({
11185			CFrame = CFrame.new(origin + u/2, origin + u);
11186			Size = Vector3.new(thickness, thickness, r.Magnitude);
11187			Color = module.StyleGuide.CFrame.UpColor3;
11188			Parent = parent;
11189		}, module.StyleGuide.CFrame.PartProperties)
11190		
11191		local back = draw({
11192			CFrame = CFrame.new(origin + b/2, origin + b);
11193			Size = Vector3.new(thickness, thickness, u.Magnitude);
11194			Color = module.StyleGuide.CFrame.BackColor3;
11195			Parent = parent;
11196		}, module.StyleGuide.CFrame.PartProperties)
11197		
11198		return right, up, back
11199	end
11200	
11201	-- Return
11202	
11203	return module
11204end
11205function _Draw2D()
11206	local module = {}
11207	
11208	-- Style Guide
11209	
11210	module.StyleGuide = {
11211		Point = {
11212			BorderSizePixel = 0;
11213			Size = UDim2.new(0, 4, 0, 4);
11214			BorderColor3 = Color3.new(0, 0, 0);
11215			BackgroundColor3 = Color3.new(0, 1, 0);
11216		},
11217		
11218		Line = {
11219			Thickness = 1;
11220			BorderSizePixel = 0;
11221			BorderColor3 = Color3.new(0, 0, 0);
11222			BackgroundColor3 = Color3.new(0, 1, 0);
11223		},
11224		
11225		Ray = {
11226			Thickness = 1;
11227			BorderSizePixel = 0;
11228			BorderColor3 = Color3.new(0, 0, 0);
11229			BackgroundColor3 = Color3.new(0, 1, 0);
11230		},
11231		
11232		Triangle = {
11233			ImageTransparency = 0;
11234			ImageColor3 = Color3.new(0, 1, 0);
11235		}
11236	}
11237	
11238	-- CONSTANTS
11239	
11240	local HALF = Vector2.new(0.5, 0.5)
11241	
11242	local RIGHT = "rbxassetid://2798177521"
11243	local LEFT = "rbxassetid://2798177955"
11244	
11245	local IMG = Instance.new("ImageLabel")
11246	IMG.BackgroundTransparency = 1
11247	IMG.AnchorPoint = HALF
11248	IMG.BorderSizePixel = 0
11249	
11250	local FRAME = Instance.new("Frame")
11251	FRAME.BorderSizePixel = 0
11252	FRAME.Size = UDim2.new(0, 0, 0, 0)
11253	FRAME.BackgroundColor3 = Color3.new(1, 1, 1)
11254	
11255	-- Functions
11256	
11257	function draw(properties, style)
11258		local frame = FRAME:Clone()
11259		for k, v in next, properties do
11260			frame[k] = v
11261		end
11262		if (style) then
11263			for k, v in next, style do
11264				if (k ~= "Thickness") then
11265					frame[k] = v
11266				end
11267			end
11268		end
11269		return frame
11270	end
11271	
11272	function module.Draw(parent, properties)
11273		properties.Parent = parent
11274		return draw(properties, nil)
11275	end
11276	
11277	function module.Point(parent, v2)
11278		return draw({
11279			AnchorPoint = HALF;
11280			Position = UDim2.new(0, v2.x, 0, v2.y);
11281			Parent = parent;
11282		}, module.StyleGuide.Point)
11283	end
11284	
11285	function module.Line(parent, a, b)
11286		local v = (b - a)
11287		local m = (a + b)/2
11288		
11289		return draw({
11290			AnchorPoint = HALF;
11291			Position = UDim2.new(0, m.x, 0, m.y);
11292			Size = UDim2.new(0, module.StyleGuide.Line.Thickness, 0, v.magnitude);
11293			Rotation = math.deg(math.atan2(v.y, v.x)) - 90;
11294			BackgroundColor3 = Color3.new(1, 1, 0);
11295			Parent = parent;
11296		}, module.StyleGuide.Line)
11297	end
11298	
11299	function module.Ray(parent, origin, direction)
11300		local a, b = origin, origin + direction
11301		local v = (b - a)
11302		local m = (a + b)/2
11303		
11304		return draw({
11305			AnchorPoint = HALF;
11306			Position = UDim2.new(0, m.x, 0, m.y);
11307			Size = UDim2.new(0, module.StyleGuide.Ray.Thickness, 0, v.magnitude);
11308			Rotation = math.deg(math.atan2(v.y, v.x)) - 90;
11309			Parent = parent;
11310		}, module.StyleGuide.Ray)
11311	end
11312	
11313	function module.Triangle(parent, a, b, c)
11314		local ab, ac, bc = b - a, c - a, c - b
11315		local abd, acd, bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc)
11316		
11317		if (abd > acd and abd > bcd) then
11318			c, a = a, c
11319		elseif (acd > bcd and acd > abd) then
11320			a, b = b, a
11321		end
11322		
11323		ab, ac, bc = b - a, c - a, c - b
11324		
11325		local unit = bc.unit
11326		local height = unit:Cross(ab)
11327		local flip = (height >= 0)
11328		local theta = math.deg(math.atan2(unit.y, unit.x)) + (flip and 0 or 180)
11329		
11330		local m1 = (a + b)/2
11331		local m2 = (a + c)/2
11332		
11333		local w1 = IMG:Clone()
11334		w1.Image = flip and RIGHT or LEFT
11335		w1.AnchorPoint = HALF
11336		w1.Size = UDim2.new(0, math.abs(unit:Dot(ab)), 0, height)
11337		w1.Position = UDim2.new(0, m1.x, 0, m1.y)
11338		w1.Rotation = theta
11339		w1.Parent = parent
11340		
11341		local w2 = IMG:Clone()
11342		w2.Image = flip and LEFT or RIGHT
11343		w2.AnchorPoint = HALF
11344		w2.Size = UDim2.new(0, math.abs(unit:Dot(ac)), 0, height)
11345		w2.Position = UDim2.new(0, m2.x, 0, m2.y)
11346		w2.Rotation = theta
11347		w2.Parent = parent
11348		
11349		for k, v in next, module.StyleGuide.Triangle do
11350			w1[k] = v
11351			w2[k] = v
11352		end
11353		
11354		return w1, w2
11355	end
11356	
11357	-- Return
11358	
11359	return module
11360end
11361function _DrawClass()
11362	local Draw2DModule = _Draw2D()
11363	local Draw3DModule = _Draw3D()
11364	
11365	--
11366	
11367	local DrawClass = {}
11368	local DrawClassStorage = setmetatable({}, {__mode = "k"})
11369	DrawClass.__index = DrawClass
11370	
11371	function DrawClass.new(parent)
11372		local self = setmetatable({}, DrawClass)
11373		
11374		self.Parent = parent
11375		DrawClassStorage[self] = {}
11376		
11377		self.Draw3D = {}
11378		for key, func in next, Draw3DModule do
11379			self.Draw3D[key] = function(...)
11380				local returns = {func(self.Parent, ...)}
11381				for i = 1, #returns do
11382					table.insert(DrawClassStorage[self], returns[i])
11383				end
11384				return unpack(returns)
11385			end
11386		end
11387		
11388		self.Draw2D = {}
11389		for key, func in next, Draw2DModule do
11390			self.Draw2D[key] = function(...)
11391				local returns = {func(self.Parent, ...)}
11392				for i = 1, #returns do
11393					table.insert(DrawClassStorage[self], returns[i])
11394				end
11395				return unpack(returns)
11396			end
11397		end
11398		
11399		return self
11400	end
11401	
11402	--
11403	
11404	function DrawClass:Clear()
11405		local t = DrawClassStorage[self]
11406		while (#t > 0) do
11407			local part = table.remove(t)
11408			if (part) then
11409				part:Destroy()
11410			end
11411		end
11412		DrawClassStorage[self] = {}
11413	end
11414	
11415	--
11416	
11417	return DrawClass
11418end
11419
11420
11421--END TEST
11422
11423local PLAYERS = game:GetService("Players")
11424
11425local GravityController = _GravityController()
11426local Controller = GravityController.new(PLAYERS.LocalPlayer)
11427
11428local DrawClass = _DrawClass()
11429
11430local PI2 = math.pi*2
11431local ZERO = Vector3.new(0, 0, 0)
11432
11433local LOWER_RADIUS_OFFSET = 3 
11434local NUM_DOWN_RAYS = 24
11435local ODD_DOWN_RAY_START_RADIUS = 3	
11436local EVEN_DOWN_RAY_START_RADIUS = 2
11437local ODD_DOWN_RAY_END_RADIUS = 1.66666
11438local EVEN_DOWN_RAY_END_RADIUS = 1
11439
11440local NUM_FEELER_RAYS = 9
11441local FEELER_LENGTH = 2
11442local FEELER_START_OFFSET = 2
11443local FEELER_RADIUS = 3.5
11444local FEELER_APEX_OFFSET = 1
11445local FEELER_WEIGHTING = 8
11446
11447function GetGravityUp(self, oldGravityUp)
11448	local ignoreList = {}
11449	for i, player in next, PLAYERS:GetPlayers() do
11450		ignoreList[i] = player.Character
11451	end
11452	
11453	-- get the normal
11454	
11455	local hrpCF = self.HRP.CFrame
11456	local isR15 = (self.Humanoid.RigType == Enum.HumanoidRigType.R15)
11457	
11458	local origin = isR15 and hrpCF.p or hrpCF.p + 0.35*oldGravityUp
11459	local radialVector = math.abs(hrpCF.LookVector:Dot(oldGravityUp)) < 0.999 and hrpCF.LookVector:Cross(oldGravityUp) or hrpCF.RightVector:Cross(oldGravityUp)
11460	
11461	local centerRayLength = 25
11462	local centerRay = Ray.new(origin, -centerRayLength * oldGravityUp)
11463	local centerHit, centerHitPoint, centerHitNormal = workspace:FindPartOnRayWithIgnoreList(centerRay, ignoreList)
11464	
11465	--[[disable
11466	DrawClass:Clear()
11467	DrawClass.Draw3D.Ray(centerRay.Origin, centerRay.Direction)
11468	]]
11469	local downHitCount = 0
11470	local totalHitCount = 0
11471	local centerRayHitCount = 0
11472	local evenRayHitCount = 0
11473	local oddRayHitCount = 0
11474	
11475	local mainDownNormal = ZERO
11476	if (centerHit) then
11477		mainDownNormal = centerHitNormal
11478		centerRayHitCount = 0
11479	end
11480	
11481	local downRaySum = ZERO
11482	for i = 1, NUM_DOWN_RAYS do
11483		local dtheta = PI2 * ((i-1)/NUM_DOWN_RAYS)
11484		
11485		local angleWeight = 0.25 + 0.75 * math.abs(math.cos(dtheta))
11486		local isEvenRay = (i%2 == 0)
11487		local startRadius = isEvenRay and EVEN_DOWN_RAY_START_RADIUS or ODD_DOWN_RAY_START_RADIUS	
11488		local endRadius = isEvenRay and EVEN_DOWN_RAY_END_RADIUS or ODD_DOWN_RAY_END_RADIUS
11489		local downRayLength = centerRayLength
11490		
11491		local offset = CFrame.fromAxisAngle(oldGravityUp, dtheta) * radialVector
11492		local dir = (LOWER_RADIUS_OFFSET * -oldGravityUp + (endRadius - startRadius) * offset)
11493		local ray = Ray.new(origin + startRadius * offset, downRayLength * dir.unit)
11494		local hit, hitPoint, hitNormal = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
11495		--[[disable
11496		DrawClass.Draw3D.Ray(ray.Origin, ray.Direction)
11497		]]
11498		if (hit) then
11499			downRaySum = downRaySum + angleWeight * hitNormal
11500			downHitCount = downHitCount + 1
11501			if isEvenRay then
11502				evenRayHitCount = evenRayHitCount + 1					
11503			else
11504				oddRayHitCount = oddRayHitCount + 1
11505			end
11506		end
11507	end
11508	
11509	local feelerHitCount = 0	
11510	local feelerNormalSum = ZERO
11511	
11512	for i = 1, NUM_FEELER_RAYS do
11513		local dtheta = 2 * math.pi * ((i-1)/NUM_FEELER_RAYS)
11514		local angleWeight =  0.25 + 0.75 * math.abs(math.cos(dtheta))	
11515		local offset = CFrame.fromAxisAngle(oldGravityUp, dtheta) * radialVector
11516		local dir = (FEELER_RADIUS * offset + LOWER_RADIUS_OFFSET * -oldGravityUp).unit
11517		local feelerOrigin = origin - FEELER_APEX_OFFSET * -oldGravityUp + FEELER_START_OFFSET * dir
11518		local ray = Ray.new(feelerOrigin, FEELER_LENGTH * dir)
11519		local hit, hitPoint, hitNormal = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
11520		--[[disable
11521		DrawClass.Draw3D.Ray(ray.Origin, ray.Direction)
11522		]]
11523		if (hit) then
11524			feelerNormalSum = feelerNormalSum + FEELER_WEIGHTING * angleWeight * hitNormal --* hitDistSqInv
11525			feelerHitCount = feelerHitCount + 1
11526		end
11527	end
11528	
11529	if (centerRayHitCount + downHitCount + feelerHitCount > 0) then
11530		local normalSum = mainDownNormal + downRaySum + feelerNormalSum
11531		if (normalSum ~= ZERO) then
11532			return normalSum.unit
11533		end
11534	end
11535	
11536	return oldGravityUp
11537end
11538
11539Controller.GetGravityUp = GetGravityUp
11540
11541-- E is toggle
11542game:GetService("ContextActionService"):BindAction("Toggle", function(action, state, input)
11543	if not (state == Enum.UserInputState.Begin) then
11544		return
11545	end
11546	
11547	if (Controller) then
11548		Controller:Destroy()
11549		Controller = nil
11550	else
11551		Controller = GravityController.new(PLAYERS.LocalPlayer)
11552		Controller.GetGravityUp = GetGravityUp
11553	end
11554end, false, Enum.KeyCode.Z)
11555print("end")