Ryan_Lubia

Walk On walls

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