noobsad

Attach to walls

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