· 8 years ago · Jul 23, 2018, 02:26 AM
1--This is a total of 17 Scripts
2--Made by SkeletalReality
3--Check out my yootoob: https://www.youtube.com/c/spookyscarehskeleton/
4--And my tweeeter: https://www.twitter.com/SkeletalReality
5
6local plr = game.Players.LocalPlayer;
7local PlayerScripts = plr.PlayerScripts;
8local NewCameraScript = script:WaitForChild('CameraScript');
9local CameraScript = PlayerScripts:WaitForChild('CameraScript');
10
11CameraScript:remove()
12NewCameraScript.Parent = PlayerScripts
13NewCameraScript.Disabled = false
14wait();
15script:remove()
16
17local RunService = game:GetService('RunService')
18local UserInputService = game:GetService('UserInputService')
19local PlayersService = game:GetService('Players')
20local StarterPlayer = game:GetService('StarterPlayer')
21
22
23local RootCamera = script:WaitForChild('RootCamera')
24
25local AttachCamera = require(RootCamera:WaitForChild('AttachCamera'))()
26local FixedCamera = require(RootCamera:WaitForChild('FixedCamera'))()
27local ScriptableCamera = require(RootCamera:WaitForChild('ScriptableCamera'))()
28local TrackCamera = require(RootCamera:WaitForChild('TrackCamera'))()
29local WatchCamera = require(RootCamera:WaitForChild('WatchCamera'))()
30
31local OrbitalCamera = require(RootCamera:WaitForChild('OrbitalCamera'))()
32local ClassicCamera = require(RootCamera:WaitForChild('ClassicCamera'))()
33local FollowCamera = require(RootCamera:WaitForChild('FollowCamera'))()
34local PopperCam = require(script:WaitForChild('PopperCam'))
35local Invisicam = require(script:WaitForChild('Invisicam'))
36local ClickToMove = require(script:WaitForChild('ClickToMove'))()
37local TransparencyController = require(script:WaitForChild('TransparencyController'))()
38
39local GameSettings = UserSettings().GameSettings
40
41local AllCamerasInLua = false
42local success, msg = pcall(function()
43 AllCamerasInLua = UserSettings():IsUserFeatureEnabled("UserAllCamerasInLua")
44end)
45if not success then
46 print("Couldn't get feature UserAllCamerasInLua because:" , msg)
47end
48
49local isOrbitalCameraEnabled = pcall(function() local test = Enum.CameraType.Orbital end)
50
51-- register what camera scripts we are using
52do
53 local PlayerScripts = PlayersService.LocalPlayer:WaitForChild("PlayerScripts")
54 local canRegisterCameras = pcall(function() PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Default) end)
55
56 if canRegisterCameras then
57 PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Follow)
58 PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Classic)
59
60 PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Default)
61 PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Follow)
62 PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Classic)
63 end
64end
65
66local CameraTypeEnumMap =
67{
68 [Enum.CameraType.Attach] = AttachCamera;
69 [Enum.CameraType.Fixed] = FixedCamera;
70 [Enum.CameraType.Scriptable] = ScriptableCamera;
71 [Enum.CameraType.Track] = TrackCamera;
72 [Enum.CameraType.Watch] = WatchCamera;
73 [Enum.CameraType.Follow] = FollowCamera;
74}
75
76if isOrbitalCameraEnabled then
77 CameraTypeEnumMap[Enum.CameraType.Orbital] = OrbitalCamera;
78end
79
80local EnabledCamera = nil
81local EnabledOcclusion = nil
82
83local cameraSubjectChangedConn = nil
84local cameraTypeChangedConn = nil
85local renderSteppedConn = nil
86
87local lastInputType = nil
88local hasLastInput = false
89
90local function IsTouch()
91 return UserInputService.TouchEnabled
92end
93
94local function shouldUsePlayerScriptsCamera()
95 local player = PlayersService.LocalPlayer
96 local currentCamera = workspace.CurrentCamera
97 if AllCamerasInLua then
98 return true
99 else
100 if player then
101 if currentCamera == nil or (currentCamera.CameraType == Enum.CameraType.Custom)
102 or (isOrbitalCameraEnabled and currentCamera.CameraType == Enum.CameraType.Orbital) then
103 return true
104 end
105 end
106 end
107 return false
108end
109
110local function isClickToMoveOn()
111 local usePlayerScripts = shouldUsePlayerScriptsCamera()
112 local player = PlayersService.LocalPlayer
113 if usePlayerScripts and player then
114 if (hasLastInput and lastInputType == Enum.UserInputType.Touch) or IsTouch() then -- Touch
115 if player.DevTouchMovementMode == Enum.DevTouchMovementMode.ClickToMove or
116 (player.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice and GameSettings.TouchMovementMode == Enum.TouchMovementMode.ClickToMove) then
117 return true
118 end
119 else -- Computer
120 if player.DevComputerMovementMode == Enum.DevComputerMovementMode.ClickToMove or
121 (player.DevComputerMovementMode == Enum.DevComputerMovementMode.UserChoice and GameSettings.ComputerMovementMode == Enum.ComputerMovementMode.ClickToMove) then
122 return true
123 end
124 end
125 end
126 return false
127end
128
129local function getCurrentCameraMode()
130 local usePlayerScripts = shouldUsePlayerScriptsCamera()
131 local player = PlayersService.LocalPlayer
132 if usePlayerScripts and player then
133 if (hasLastInput and lastInputType == Enum.UserInputType.Touch) or IsTouch() then -- Touch (iPad, etc...)
134 if isClickToMoveOn() then
135 return Enum.DevTouchMovementMode.ClickToMove.Name
136 elseif player.DevTouchCameraMode == Enum.DevTouchCameraMovementMode.UserChoice then
137 local touchMovementMode = GameSettings.TouchCameraMovementMode
138 if touchMovementMode == Enum.TouchCameraMovementMode.Default then
139 return Enum.TouchCameraMovementMode.Follow.Name
140 end
141 return touchMovementMode.Name
142 else
143 return player.DevTouchCameraMode.Name
144 end
145 else -- Computer
146 if isClickToMoveOn() then
147 return Enum.DevComputerMovementMode.ClickToMove.Name
148 elseif player.DevComputerCameraMode == Enum.DevComputerCameraMovementMode.UserChoice then
149 local computerMovementMode = GameSettings.ComputerCameraMovementMode
150 if computerMovementMode == Enum.ComputerCameraMovementMode.Default then
151 return Enum.ComputerCameraMovementMode.Classic.Name
152 end
153 return computerMovementMode.Name
154 else
155 return player.DevComputerCameraMode.Name
156 end
157 end
158 end
159end
160
161local function getCameraOcclusionMode()
162 local usePlayerScripts = shouldUsePlayerScriptsCamera()
163 local player = PlayersService.LocalPlayer
164 if usePlayerScripts and player then
165 return player.DevCameraOcclusionMode
166 end
167end
168
169local function Update()
170 if EnabledCamera then
171 EnabledCamera:Update()
172 EnabledCamera:ApplyVRTransform()
173 end
174
175 if EnabledOcclusion then
176 EnabledOcclusion:Update(EnabledCamera)
177 end
178
179 if shouldUsePlayerScriptsCamera() then
180 TransparencyController:Update()
181 end
182end
183
184local function SetEnabledCamera(newCamera)
185 if EnabledCamera ~= newCamera then
186 if EnabledCamera then
187 EnabledCamera:SetEnabled(false)
188 end
189 EnabledCamera = newCamera
190 if EnabledCamera then
191 EnabledCamera:SetEnabled(true)
192 end
193 end
194end
195
196local function OnCameraMovementModeChange(newCameraMode)
197 if newCameraMode == Enum.DevComputerMovementMode.ClickToMove.Name then
198 ClickToMove:Start()
199 SetEnabledCamera(nil)
200 TransparencyController:SetEnabled(true)
201 else
202 local currentCameraType = workspace.CurrentCamera and workspace.CurrentCamera.CameraType
203
204 if (currentCameraType == Enum.CameraType.Custom or not AllCamerasInLua) and newCameraMode == Enum.ComputerCameraMovementMode.Classic.Name then
205 SetEnabledCamera(ClassicCamera)
206 TransparencyController:SetEnabled(true)
207 elseif (currentCameraType == Enum.CameraType.Custom or not AllCamerasInLua) and newCameraMode == Enum.ComputerCameraMovementMode.Follow.Name then
208 SetEnabledCamera(FollowCamera)
209 TransparencyController:SetEnabled(true)
210 elseif (currentCameraType == Enum.CameraType.Custom or not AllCamerasInLua) and (isOrbitalCameraEnabled and (newCameraMode == Enum.ComputerCameraMovementMode.Orbital.Name)) then
211 SetEnabledCamera(OrbitalCamera)
212 TransparencyController:SetEnabled(true)
213 elseif AllCamerasInLua and CameraTypeEnumMap[currentCameraType] then
214 SetEnabledCamera(CameraTypeEnumMap[currentCameraType])
215 TransparencyController:SetEnabled(false)
216 else -- Our camera movement code was disabled by the developer
217 SetEnabledCamera(nil)
218 TransparencyController:SetEnabled(false)
219 end
220 ClickToMove:Stop()
221 end
222
223 local newOcclusionMode = getCameraOcclusionMode()
224 if EnabledOcclusion == Invisicam and newOcclusionMode ~= Enum.DevCameraOcclusionMode.Invisicam then
225 Invisicam:Cleanup()
226 end
227
228 -- PopperCam does not work with OrbitalCamera, as OrbitalCamera's distance can be fixed.
229 if newOcclusionMode == Enum.DevCameraOcclusionMode.Zoom and ( isOrbitalCameraEnabled and newCameraMode ~= Enum.ComputerCameraMovementMode.Orbital.Name ) then
230 EnabledOcclusion = PopperCam
231 elseif newOcclusionMode == Enum.DevCameraOcclusionMode.Invisicam then
232 EnabledOcclusion = Invisicam
233 else
234 EnabledOcclusion = false
235 end
236end
237
238local function OnCameraTypeChanged(newCameraType)
239 if newCameraType == Enum.CameraType.Scriptable then
240 if UserInputService.MouseBehavior == Enum.MouseBehavior.LockCenter then
241 UserInputService.MouseBehavior = Enum.MouseBehavior.Default
242 end
243 end
244end
245
246
247local function OnCameraSubjectChanged(newSubject)
248 TransparencyController:SetSubject(newSubject)
249end
250
251local function OnNewCamera()
252 OnCameraMovementModeChange(getCurrentCameraMode())
253
254 local currentCamera = workspace.CurrentCamera
255 if currentCamera then
256 if cameraSubjectChangedConn then
257 cameraSubjectChangedConn:disconnect()
258 end
259
260 if cameraTypeChangedConn then
261 cameraTypeChangedConn:disconnect()
262 end
263
264 cameraSubjectChangedConn = currentCamera:GetPropertyChangedSignal("CameraSubject"):connect(function()
265 OnCameraSubjectChanged(currentCamera.CameraSubject)
266 end)
267
268 cameraTypeChangedConn = currentCamera:GetPropertyChangedSignal("CameraType"):connect(function()
269 OnCameraMovementModeChange(getCurrentCameraMode())
270 OnCameraTypeChanged(currentCamera.CameraType)
271 end)
272
273 OnCameraSubjectChanged(currentCamera.CameraSubject)
274 OnCameraTypeChanged(currentCamera.CameraType)
275 end
276end
277
278
279local function OnPlayerAdded(player)
280 workspace.Changed:connect(function(prop)
281 if prop == 'CurrentCamera' then
282 OnNewCamera()
283 end
284 end)
285
286 player.Changed:connect(function(prop)
287 OnCameraMovementModeChange(getCurrentCameraMode())
288 end)
289
290 GameSettings.Changed:connect(function(prop)
291 OnCameraMovementModeChange(getCurrentCameraMode())
292 end)
293
294 RunService:BindToRenderStep("cameraRenderUpdate", Enum.RenderPriority.Camera.Value, Update)
295
296 OnNewCamera()
297 OnCameraMovementModeChange(getCurrentCameraMode())
298end
299
300do
301 while PlayersService.LocalPlayer == nil do PlayersService.PlayerAdded:wait() end
302 hasLastInput = pcall(function()
303 lastInputType = UserInputService:GetLastInputType()
304 UserInputService.LastInputTypeChanged:connect(function(newLastInputType)
305 lastInputType = newLastInputType
306 end)
307 end)
308 OnPlayerAdded(PlayersService.LocalPlayer)
309end
310
311-- Written By Kip Turner, Copyright Roblox 2014
312
313local newClickToMove = script:FindFirstChild("NewClickToMove")
314if newClickToMove then
315 local newClickToMoveFlagSuccess, newClickToMoveFlagEnabled = pcall(function()
316 return UserSettings():IsUserFeatureEnabled("UserUseNewClickToMove")
317 end)
318 local useNewClickToMove = newClickToMoveFlagSuccess and newClickToMoveFlagEnabled
319 if useNewClickToMove then
320 return require(newClickToMove)
321 end
322end
323
324local UIS = game:GetService("UserInputService")
325local PathfindingService = game:GetService("PathfindingService")
326local PlayerService = game:GetService("Players")
327local RunService = game:GetService("RunService")
328local DebrisService = game:GetService('Debris')
329local ReplicatedStorage = game:GetService('ReplicatedStorage')
330
331local CameraScript = script.Parent
332local ClassicCameraModule = require(CameraScript:WaitForChild('RootCamera'):WaitForChild('ClassicCamera'))
333
334local Player = PlayerService.localPlayer
335local MyMouse = Player:GetMouse()
336
337
338local DirectPathEnabled = false
339local SHOW_PATH = false
340
341local Y_VECTOR3 = Vector3.new(0, 1, 0)
342local XZ_VECTOR3 = Vector3.new(1, 0, 1)
343local ZERO_VECTOR3 = Vector3.new(0, 0, 0)
344local ZERO_VECTOR2 = Vector2.new(0, 0)
345
346local RayCastIgnoreList = workspace.FindPartOnRayWithIgnoreList
347local GetPartsTouchingExtents = workspace.FindPartsInRegion3
348
349local math_min = math.min
350local math_max = math.max
351local math_pi = math.pi
352local math_floor = math.floor
353local math_abs = math.abs
354local math_deg = math.deg
355local math_acos = math.acos
356local math_sin = math.sin
357local math_atan2 = math.atan2
358
359local Vector3_new = Vector3.new
360local Vector2_new = Vector2.new
361local CFrame_new = CFrame.new
362
363local CurrentSeatPart = nil
364local DrivingTo = nil
365
366-- Bindable for when we want touch emergency controls
367-- TODO: Click to move should probably have it's own gui touch controls
368-- to manage this.
369local BindableEvent_OnFailStateChanged = nil
370local BindableEvent_EnableTouchJump = nil
371if UIS.TouchEnabled then
372 BindableEvent_OnFailStateChanged = Instance.new('BindableEvent')
373 BindableEvent_OnFailStateChanged.Name = "OnClickToMoveFailStateChange"
374 BindableEvent_EnableTouchJump = Instance.new('BindableEvent')
375 BindableEvent_EnableTouchJump.Name = "EnableTouchJump"
376 local CameraScript = script.Parent
377 local PlayerScripts = CameraScript.Parent
378 BindableEvent_OnFailStateChanged.Parent = PlayerScripts
379 BindableEvent_EnableTouchJump.Parent = PlayerScripts
380end
381
382local function clamp(low, high, num)
383 return (num > high and high or num < low and low or num)
384end
385
386--------------------------UTIL LIBRARY-------------------------------
387local Utility = {}
388do
389 local Signal = {}
390
391 function Signal.Create()
392 local sig = {}
393
394 local mSignaler = Instance.new('BindableEvent')
395
396 local mArgData = nil
397 local mArgDataCount = nil
398
399 function sig:fire(...)
400 mArgData = {...}
401 mArgDataCount = select('#', ...)
402 mSignaler:Fire()
403 end
404
405 function sig:connect(f)
406 if not f then error("connect(nil)", 2) end
407 return mSignaler.Event:connect(function()
408 f(unpack(mArgData, 1, mArgDataCount))
409 end)
410 end
411
412 function sig:wait()
413 mSignaler.Event:wait()
414 assert(mArgData, "Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.")
415 return unpack(mArgData, 1, mArgDataCount)
416 end
417
418 return sig
419 end
420 Utility.Signal = Signal
421
422 function Utility.Create(instanceType)
423 return function(data)
424 local obj = Instance.new(instanceType)
425 for k, v in pairs(data) do
426 if type(k) == 'number' then
427 v.Parent = obj
428 else
429 obj[k] = v
430 end
431 end
432 return obj
433 end
434 end
435
436 local function ViewSizeX()
437 local camera = workspace.CurrentCamera
438 local x = camera and camera.ViewportSize.X or 0
439 local y = camera and camera.ViewportSize.Y or 0
440 if x == 0 then
441 return 1024
442 else
443 if x > y then
444 return x
445 else
446 return y
447 end
448 end
449 end
450 Utility.ViewSizeX = ViewSizeX
451
452 local function ViewSizeY()
453 local camera = workspace.CurrentCamera
454 local x = camera and camera.ViewportSize.X or 0
455 local y = camera and camera.ViewportSize.Y or 0
456 if y == 0 then
457 return 768
458 else
459 if x > y then
460 return y
461 else
462 return x
463 end
464 end
465 end
466 Utility.ViewSizeY = ViewSizeY
467
468 local function AspectRatio()
469 return ViewSizeX() / ViewSizeY()
470 end
471 Utility.AspectRatio = AspectRatio
472
473 local function FindChacterAncestor(part)
474 if part then
475 local humanoid = part:FindFirstChild("Humanoid")
476 if humanoid then
477 return part, humanoid
478 else
479 return FindChacterAncestor(part.Parent)
480 end
481 end
482 end
483 Utility.FindChacterAncestor = FindChacterAncestor
484
485
486 local function GetUnitRay(x, y, viewWidth, viewHeight, camera)
487 return camera:ScreenPointToRay(x, y)
488 end
489 Utility.GetUnitRay = GetUnitRay
490
491 local function Raycast(ray, ignoreNonCollidable, ignoreList)
492 local ignoreList = ignoreList or {}
493 local hitPart, hitPos = RayCastIgnoreList(workspace, ray, ignoreList)
494 if hitPart then
495 if ignoreNonCollidable and hitPart.CanCollide == false then
496 table.insert(ignoreList, hitPart)
497 return Raycast(ray, ignoreNonCollidable, ignoreList)
498 end
499 return hitPart, hitPos
500 end
501 return nil, nil
502 end
503 Utility.Raycast = Raycast
504
505
506 Utility.Round = function(num, roundToNearest)
507 roundToNearest = roundToNearest or 1
508 return math_floor((num + roundToNearest/2) / roundToNearest) * roundToNearest
509 end
510
511 local function AveragePoints(positions)
512 local avgPos = ZERO_VECTOR2
513 if #positions > 0 then
514 for i = 1, #positions do
515 avgPos = avgPos + positions[i]
516 end
517 avgPos = avgPos / #positions
518 end
519 return avgPos
520 end
521 Utility.AveragePoints = AveragePoints
522
523 local function FuzzyEquals(numa, numb)
524 return numa + 0.1 > numb and numa - 0.1 < numb
525 end
526 Utility.FuzzyEquals = FuzzyEquals
527
528 local LastInput = 0
529 UIS.InputBegan:connect(function(inputObject, wasSunk)
530 if not wasSunk then
531 if inputObject.UserInputType == Enum.UserInputType.Touch or
532 inputObject.UserInputType == Enum.UserInputType.MouseButton1 or
533 inputObject.UserInputType == Enum.UserInputType.MouseButton2 then
534 LastInput = tick()
535 end
536 end
537 end)
538 Utility.GetLastInput = function()
539 return LastInput
540 end
541end
542
543local humanoidCache = {}
544local function findPlayerHumanoid(player)
545 local character = player and player.Character
546 if character then
547 local resultHumanoid = humanoidCache[player]
548 if resultHumanoid and resultHumanoid.Parent == character then
549 return resultHumanoid
550 else
551 humanoidCache[player] = nil -- Bust Old Cache
552 local humanoid = character:FindFirstChildOfClass("Humanoid")
553 if humanoid then
554 humanoidCache[player] = humanoid
555 end
556 return humanoid
557 end
558 end
559end
560
561local GetThetaBetweenCFrames; do
562 local components = CFrame.new().components
563 local inverse = CFrame.new().inverse
564 local acos = math.acos
565
566 GetThetaBetweenCFrames = function(c0, c1) -- (CFrame from, CFrame to) -> (float theta)
567 local _, _, _, xx, yx, zx,
568 xy, yy, zy,
569 xz, yz, zz = components(inverse(c0)*c1)
570 local cosTheta = (xx + yy + zz - 1)/2
571
572 if cosTheta >= 0.999 then
573 -- Same rotation
574 return 0
575 elseif cosTheta <= -0.999 then
576 -- Oposite rotations
577 return math_pi
578 else
579 return acos(cosTheta)
580 end
581 end
582end
583
584---------------------------------------------------------
585
586local Signal = Utility.Signal
587local Create = Utility.Create
588
589--------------------------CHARACTER CONTROL-------------------------------
590local function CreateController()
591 local this = {}
592
593 this.TorsoLookPoint = nil
594
595 function this:SetTorsoLookPoint(point)
596 local humanoid = findPlayerHumanoid(Player)
597 if humanoid then
598 humanoid.AutoRotate = false
599 end
600 this.TorsoLookPoint = point
601 self:UpdateTorso()
602 delay(2,
603 function()
604 -- this isnt technically correct for detecting if this is the last issue to the setTorso function
605 if this.TorsoLookPoint == point then
606 this.TorsoLookPoint = nil
607 if humanoid then
608 humanoid.AutoRotate = true
609 end
610 end
611 end)
612 end
613
614 function this:UpdateTorso(point)
615 if this.TorsoLookPoint then
616 point = this.TorsoLookPoint
617 else
618 return
619 end
620
621 local humanoid = findPlayerHumanoid(Player)
622 local torso = humanoid and humanoid.Torso
623 if torso then
624 local lookVec = (point - torso.CFrame.p).unit
625 local squashedLookVec = Vector3_new(lookVec.X, 0, lookVec.Z).unit
626 torso.CFrame = CFrame.new(torso.CFrame.p, torso.CFrame.p + squashedLookVec)
627 end
628 end
629
630 return this
631end
632
633local CharacterControl = CreateController()
634-----------------------------------------------------------------------
635
636--------------------------PC AUTO JUMPER-------------------------------
637
638local function GetCharacter()
639 return Player and Player.Character
640end
641
642local function GetTorso()
643 local humanoid = findPlayerHumanoid(Player)
644 return humanoid and humanoid.Torso
645end
646
647local function IsPartAHumanoid(part)
648 return part and part.Parent and (part.Parent:FindFirstChild('Humanoid') ~= nil)
649end
650
651local function doAutoJump()
652 local character = GetCharacter()
653 if (character == nil) then
654 return;
655 end
656
657 local humanoid = findPlayerHumanoid(Player)
658 if (humanoid == nil) then
659 return;
660 end
661
662 local rayLength = 1.5;
663 -- This is how high a ROBLOXian jumps from the mid point of his torso
664 local jumpHeight = 7.0;
665
666 local torso = GetTorso()
667 if (torso == nil) then
668 return;
669 end
670
671 local torsoCFrame = torso.CFrame;
672 local torsoLookVector = torsoCFrame.lookVector;
673 local torsoPos = torsoCFrame.p;
674
675 local torsoRay = Ray.new(torsoPos + Vector3_new(0, -torso.Size.Y/2, 0), torsoLookVector * rayLength);
676 local jumpRay = Ray.new(torsoPos + Vector3_new(0, jumpHeight - torso.Size.Y, 0), torsoLookVector * rayLength);
677
678 local hitPart, _ = RayCastIgnoreList(workspace, torsoRay, {character}, false)
679 local jumpHitPart, _ = RayCastIgnoreList(workspace, jumpRay, {character}, false)
680
681 if (hitPart and jumpHitPart == nil and hitPart.CanCollide == true) then
682 -- NOTE: this follow line is not in the C++ impl, but an improvement in Click to Move
683 if not IsPartAHumanoid(hitPart) then
684 humanoid.Jump = true;
685 end
686 end
687end
688
689local NO_JUMP_STATES =
690{
691 [Enum.HumanoidStateType.FallingDown] = false;
692 [Enum.HumanoidStateType.Flying] = false;
693 [Enum.HumanoidStateType.Freefall] = false;
694 [Enum.HumanoidStateType.GettingUp] = false;
695 [Enum.HumanoidStateType.Ragdoll] = false;
696 [Enum.HumanoidStateType.Running] = false;
697 [Enum.HumanoidStateType.Seated] = false;
698 [Enum.HumanoidStateType.Swimming] = false;
699
700 -- Special case to detect if we are on a ladder
701 [Enum.HumanoidStateType.Climbing] = false;
702}
703
704local function enableAutoJump()
705 local humanoid = findPlayerHumanoid(Player)
706 local currentState = humanoid and humanoid:GetState()
707 if currentState then
708 return NO_JUMP_STATES[currentState] == nil
709 end
710 return false
711end
712
713local function getAutoJump()
714 return true
715end
716
717local function vec3IsZero(vec3)
718 return vec3.magnitude < 0.05
719end
720
721-- NOTE: This function is radically different from the engine's implementation
722local walkVelocityVector = Vector3_new(1,1,1)
723local function calcDesiredWalkVelocity()
724 -- TEMP
725 return walkVelocityVector
726end
727
728local function preStepSimulatorSide(dt)
729 if getAutoJump() and enableAutoJump() then
730 local desiredWalkVelocity = calcDesiredWalkVelocity();
731 if (not vec3IsZero(desiredWalkVelocity)) then
732 doAutoJump();
733 end
734 end
735end
736
737local function AutoJumper()
738 local this = {}
739 local running = false
740 local runRoutine = nil
741
742 function this:Run()
743 running = true
744 local thisRoutine = nil
745 thisRoutine = coroutine.create(function()
746 while running and thisRoutine == runRoutine do
747 this:Step()
748 wait()
749 end
750 end)
751 runRoutine = thisRoutine
752 coroutine.resume(thisRoutine)
753 end
754
755 function this:Stop()
756 running = false
757 end
758
759 function this:Step()
760 preStepSimulatorSide()
761 end
762
763 return this
764end
765
766-----------------------------------------------------------------------------
767
768-----------------------------------PATHER--------------------------------------
769
770local function CreateDestinationIndicator(pos)
771 local destinationGlobe = Create'Part'
772 {
773 Name = 'PathGlobe';
774 TopSurface = 'Smooth';
775 BottomSurface = 'Smooth';
776 Shape = 'Ball';
777 CanCollide = false;
778 Size = Vector3_new(2,2,2);
779 BrickColor = BrickColor.new('Institutional white');
780 Transparency = 0;
781 Anchored = true;
782 CFrame = CFrame.new(pos);
783 }
784 return destinationGlobe
785end
786
787local function Pather(character, point)
788 local this = {}
789
790 this.Cancelled = false
791 this.Started = false
792
793 this.Finished = Signal.Create()
794 this.PathFailed = Signal.Create()
795 this.PathStarted = Signal.Create()
796
797 this.PathComputed = false
798
799 function this:YieldUntilPointReached(character, point, timeout)
800 timeout = timeout or 10000000
801
802 local humanoid = findPlayerHumanoid(Player)
803 local torso = humanoid and humanoid.Torso
804 local start = tick()
805 local lastMoveTo = start
806 while torso and tick() - start < timeout and this.Cancelled == false do
807 local diffVector = (point - torso.CFrame.p)
808 local xzMagnitude = (diffVector * XZ_VECTOR3).magnitude
809 if xzMagnitude < 6 then
810 -- Jump if the path is telling is to go upwards
811 if diffVector.Y >= 2.2 then
812 humanoid.Jump = true
813 end
814 end
815 -- The hard-coded number 2 here is from the engine's MoveTo implementation
816 if xzMagnitude < 2 then
817 return true
818 end
819 -- Keep on issuing the move command because it will automatically quit every so often.
820 if tick() - lastMoveTo > 1.5 then
821 humanoid:MoveTo(point)
822 lastMoveTo = tick()
823 end
824 CharacterControl:UpdateTorso(point)
825 wait()
826 end
827 return false
828 end
829
830 function this:Cancel()
831 this.Cancelled = true
832 local humanoid = findPlayerHumanoid(Player)
833 local torso = humanoid and humanoid.Torso
834 if humanoid and torso then
835 humanoid:MoveTo(torso.CFrame.p)
836 end
837 end
838
839 function this:CheckOcclusion(point1, point2, character, torsoRadius)
840 local humanoid = findPlayerHumanoid(Player)
841 local torso = humanoid and humanoid.Torso
842 if torsoRadius == nil then
843 torsoRadius = torso and Vector3_new(torso.Size.X/2,0,torso.Size.Z/2) or XZ_VECTOR3
844 end
845
846 local diffVector = point2 - point1
847 local directionVector = diffVector.unit
848
849 local rightVector = Y_VECTOR3:Cross(directionVector) * torsoRadius
850
851 local rightPart, _ = Utility.Raycast(Ray.new(point1 + rightVector, diffVector + rightVector), true, {character})
852 local hitPart, _ = Utility.Raycast(Ray.new(point1, diffVector), true, {character})
853 local leftPart, _ = Utility.Raycast(Ray.new(point1 - rightVector, diffVector - rightVector), true, {character})
854
855 if rightPart or hitPart or leftPart then
856 return false
857 end
858
859 -- Make sure we have somewhere to stand on
860 local midPt = (point2 + point1) / 2
861 local studsBetweenSamples = 2
862 for i = 1, math_floor(diffVector.magnitude/studsBetweenSamples) do
863 local downPart, _ = Utility.Raycast(Ray.new(point1 + directionVector * i * studsBetweenSamples, Vector3_new(0,-7,0)), true, {character})
864 if not downPart then
865 return false
866 end
867 end
868
869 return true
870 end
871
872 function this:SmoothPoints(pathToSmooth)
873 local result = {}
874
875 local humanoid = findPlayerHumanoid(Player)
876 local torso = humanoid and humanoid.Torso
877 for i = 1, #pathToSmooth do
878 table.insert(result, pathToSmooth[i])
879 end
880
881 -- Backwards for safe-deletion
882 for i = #result - 1, 1, -1 do
883 if i + 1 <= #result then
884
885 local nextPoint = result[i+1]
886 local thisPoint = result[i]
887
888 local lastPoint = result[i-1]
889 if lastPoint == nil then
890 lastPoint = torso and Vector3_new(torso.CFrame.p.X, thisPoint.Y, torso.CFrame.p.Z)
891 end
892
893 if lastPoint and Utility.FuzzyEquals(thisPoint.Y, lastPoint.Y) and Utility.FuzzyEquals(thisPoint.Y, nextPoint.Y) then
894 if this:CheckOcclusion(lastPoint, nextPoint, character) then
895 table.remove(result, i)
896 -- Move i back one to recursively-smooth
897 i = i + 1
898 end
899 end
900 end
901 end
902
903 return result
904 end
905
906 function this:CheckNeighboringCells(character)
907 local pathablePoints = {}
908 local humanoid = findPlayerHumanoid(Player)
909 local torso = character and humanoid and humanoid.Torso
910 if torso then
911 local torsoCFrame = torso.CFrame
912 local torsoPos = torsoCFrame.p
913 -- Minus and plus 2 is so we can get it into the cell-corner space and then translate it back into cell-center space
914 local roundedPos = Vector3_new(Utility.Round(torsoPos.X-2,4)+2, Utility.Round(torsoPos.Y-2,4)+2, Utility.Round(torsoPos.Z-2,4)+2)
915 local neighboringCells = {}
916 for x = -4, 4, 8 do
917 for z = -4, 4, 8 do
918 table.insert(neighboringCells, roundedPos + Vector3_new(x,0,z))
919 end
920 end
921 for _, testPoint in pairs(neighboringCells) do
922 local pathable = this:CheckOcclusion(roundedPos, testPoint, character, ZERO_VECTOR3)
923 if pathable then
924 table.insert(pathablePoints, testPoint)
925 end
926 end
927 end
928 return pathablePoints
929 end
930
931 function this:ComputeDirectPath()
932 local humanoid = findPlayerHumanoid(Player)
933 local torso = humanoid and humanoid.Torso
934 if torso then
935 local startPt = torso.CFrame.p
936 local finishPt = point
937 if (finishPt - startPt).magnitude < 150 then
938 -- move back the destination by 2 studs or otherwise the pather will collide with the object we are trying to reach
939 finishPt = finishPt - (finishPt - startPt).unit * 2
940 if this:CheckOcclusion(startPt, finishPt, character, ZERO_VECTOR3) then
941 local pathResult = {}
942 pathResult.Status = Enum.PathStatus.Success
943 function pathResult:GetPointCoordinates()
944 return {finishPt}
945 end
946 return pathResult
947 end
948 end
949 end
950 end
951
952 local function AllAxisInThreshhold(targetPt, otherPt, threshold)
953 return math_abs(targetPt.X - otherPt.X) <= threshold and
954 math_abs(targetPt.Y - otherPt.Y) <= threshold and
955 math_abs(targetPt.Z - otherPt.Z) <= threshold
956 end
957
958 function this:ComputePath()
959 local smoothed = false
960 local humanoid = findPlayerHumanoid(Player)
961 local torso = humanoid and humanoid.Torso
962 if torso then
963 if this.PathComputed then return end
964 this.PathComputed = true
965 -- Will yield the script since it is an Async script (start, finish, maxDistance)
966 -- Try to use the smooth function, but it may not exist yet :(
967 local success = pcall(function()
968 -- 3 is height from torso cframe to ground
969 this.pathResult = PathfindingService:ComputeSmoothPathAsync(torso.CFrame.p - Vector3_new(0,3,0), point, 400)
970 smoothed = true
971 end)
972 if not success then
973 -- 3 is height from torso cframe to ground
974 this.pathResult = PathfindingService:ComputeRawPathAsync(torso.CFrame.p - Vector3_new(0,3,0), point, 400)
975 smoothed = false
976 end
977 this.pointList = this.pathResult and this.pathResult:GetPointCoordinates()
978 local pathFound = false
979 if this.pathResult.Status == Enum.PathStatus.FailFinishNotEmpty then
980 -- Lets try again with a slightly set back start point; it is ok to do this again so the FailFinishNotEmpty uses little computation
981 local diffVector = point - workspace.CurrentCamera.CoordinateFrame.p
982 if diffVector.magnitude > 2 then
983 local setBackPoint = point - (diffVector).unit * 2.1
984 local success = pcall(function()
985 this.pathResult = PathfindingService:ComputeSmoothPathAsync(torso.CFrame.p, setBackPoint, 400)
986 smoothed = true
987 end)
988 if not success then
989 this.pathResult = PathfindingService:ComputeRawPathAsync(torso.CFrame.p, setBackPoint, 400)
990 smoothed = false
991 end
992 this.pointList = this.pathResult and this.pathResult:GetPointCoordinates()
993 pathFound = true
994 end
995 end
996 if this.pathResult.Status == Enum.PathStatus.ClosestNoPath and #this.pointList >= 1 and pathFound == false then
997 local otherPt = this.pointList[#this.pointList]
998 if AllAxisInThreshhold(point, otherPt, 4) and (torso.CFrame.p - point).magnitude > (otherPt - point).magnitude then
999 local pathResult = {}
1000 pathResult.Status = Enum.PathStatus.Success
1001 function pathResult:GetPointCoordinates()
1002 return {this.pointList}
1003 end
1004 this.pathResult = pathResult
1005 pathFound = true
1006 end
1007 end
1008 if (this.pathResult.Status == Enum.PathStatus.FailStartNotEmpty or this.pathResult.Status == Enum.PathStatus.ClosestNoPath) and pathFound == false then
1009 local pathablePoints = this:CheckNeighboringCells(character)
1010 for _, otherStart in pairs(pathablePoints) do
1011 local pathResult;
1012 local success = pcall(function()
1013 pathResult = PathfindingService:ComputeSmoothPathAsync(otherStart, point, 400)
1014 smoothed = true
1015 end)
1016 if not success then
1017 pathResult = PathfindingService:ComputeRawPathAsync(otherStart, point, 400)
1018 smoothed = false
1019 end
1020 if pathResult and pathResult.Status == Enum.PathStatus.Success then
1021 this.pathResult = pathResult
1022 if this.pathResult then
1023 this.pointList = this.pathResult:GetPointCoordinates()
1024 table.insert(this.pointList, 1, otherStart)
1025 end
1026 break
1027 end
1028 end
1029 end
1030 if DirectPathEnabled then
1031 if this.pathResult.Status ~= Enum.PathStatus.Success then
1032 local directPathResult = this:ComputeDirectPath()
1033 if directPathResult and directPathResult.Status == Enum.PathStatus.Success then
1034 this.pathResult = directPathResult
1035 this.pointList = directPathResult:GetPointCoordinates()
1036 end
1037 end
1038 end
1039 end
1040 return smoothed
1041 end
1042
1043 function this:IsValidPath()
1044 this:ComputePath()
1045 local pathStatus = this.pathResult.Status
1046 return pathStatus == Enum.PathStatus.Success
1047 end
1048
1049 function this:GetPathStatus()
1050 this:ComputePath()
1051 return this.pathResult.Status
1052 end
1053
1054 function this:Start()
1055 if CurrentSeatPart then
1056 return
1057 end
1058 spawn(function()
1059 local humanoid = findPlayerHumanoid(Player)
1060 --humanoid.AutoRotate = false
1061 local torso = humanoid and humanoid.Torso
1062 if torso then
1063 if this.Started then return end
1064 this.Started = true
1065 -- Will yield the script since it is an Async function script (start, finish, maxDistance)
1066 local smoothed = this:ComputePath()
1067 if this:IsValidPath() then
1068 this.PathStarted:fire()
1069 -- smooth out zig-zaggy paths
1070 local smoothPath = smoothed and this.pointList or this:SmoothPoints(this.pointList)
1071 for i, point in pairs(smoothPath) do
1072 if humanoid then
1073 if this.Cancelled then
1074 return
1075 end
1076
1077 local wayPoint = nil
1078 if SHOW_PATH then
1079 wayPoint = CreateDestinationIndicator(point)
1080 wayPoint.BrickColor = BrickColor.new("New Yeller")
1081 wayPoint.Parent = workspace
1082 print(wayPoint.CFrame.p)
1083 end
1084
1085 humanoid:MoveTo(point)
1086
1087 local distance = ((torso.CFrame.p - point) * XZ_VECTOR3).magnitude
1088 local approxTime = 10
1089 if math_abs(humanoid.WalkSpeed) > 0 then
1090 approxTime = distance / math_abs(humanoid.WalkSpeed)
1091 end
1092
1093 local yielding = true
1094
1095 if i == 1 then
1096 --local rotatedCFrame = CameraModule:LookAtPreserveHeight(point)
1097 if CameraModule then
1098 local rotatedCFrame = CameraModule:LookAtPreserveHeight(smoothPath[#smoothPath])
1099 local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame)
1100 end
1101 --CharacterControl:SetTorsoLookPoint(point)
1102 end
1103 ---[[
1104 if (humanoid.Torso.CFrame.p - point).magnitude > 9 then
1105 spawn(function()
1106 while yielding and this.Cancelled == false do
1107 if CameraModule then
1108 local look = CameraModule:GetCameraLook()
1109 local squashedLook = (look * XZ_VECTOR3).unit
1110 local direction = ((point - CameraModule.cframe.p) * XZ_VECTOR3).unit
1111
1112 local theta = math_deg(math_acos(squashedLook:Dot(direction)))
1113
1114 if tick() - Utility.GetLastInput() > 2 and theta > (workspace.CurrentCamera.FieldOfView / 2) then
1115 local rotatedCFrame = CameraModule:LookAtPreserveHeight(point)
1116 local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame)
1117 --return
1118 end
1119 end
1120 wait(0.1)
1121 end
1122 end)
1123 end
1124 --]]
1125 local didReach = this:YieldUntilPointReached(character, point, approxTime * 3 + 1)
1126
1127 yielding = false
1128
1129 if SHOW_PATH then
1130 wayPoint:Destroy()
1131 end
1132
1133 if not didReach then
1134 this.PathFailed:fire()
1135 return
1136 end
1137 end
1138 end
1139
1140 this.Finished:fire()
1141 return
1142 end
1143 end
1144 this.PathFailed:fire()
1145 end)
1146 end
1147
1148 return this
1149end
1150
1151-------------------------------------------------------------------------
1152
1153local function FlashRed(object)
1154 local origColor = object.BrickColor
1155 local redColor = BrickColor.new("Really red")
1156 local start = tick()
1157 local duration = 4
1158 spawn(function()
1159 while object and tick() - start < duration do
1160 object.BrickColor = origColor
1161 wait(0.13)
1162 if object then
1163 object.BrickColor = redColor
1164 end
1165 wait(0.13)
1166 end
1167 end)
1168end
1169
1170--local joystickWidth = 250
1171--local joystickHeight = 250
1172local function IsInBottomLeft(pt)
1173 local joystickHeight = math.min(Utility.ViewSizeY() * 0.33, 250)
1174 local joystickWidth = joystickHeight
1175 return pt.X <= joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
1176end
1177
1178local function IsInBottomRight(pt)
1179 local joystickHeight = math.min(Utility.ViewSizeY() * 0.33, 250)
1180 local joystickWidth = joystickHeight
1181 return pt.X >= Utility.ViewSizeX() - joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
1182end
1183
1184local function CheckAlive(character)
1185 local humanoid = findPlayerHumanoid(Player)
1186 return humanoid ~= nil and humanoid.Health > 0
1187end
1188
1189local function GetEquippedTool(character)
1190 if character ~= nil then
1191 for _, child in pairs(character:GetChildren()) do
1192 if child:IsA('Tool') then
1193 return child
1194 end
1195 end
1196 end
1197end
1198
1199local function ExploreWithRayCast(currentPoint, originDirection)
1200 local TestDistance = 40
1201 local TestVectors = {}
1202 do
1203 local forwardVector = originDirection;
1204 for i = 0, 15 do
1205 table.insert(TestVectors, CFrame.Angles(0, math.pi / 8 * i, 0) * forwardVector)
1206 end
1207 end
1208
1209 local testResults = {}
1210 -- Heuristic should be something along the lines of distance and closeness to the traveling direction
1211 local function ExploreHeuristic()
1212 for _, testData in pairs(testResults) do
1213 local walkDirection = -1 * originDirection
1214 local directionCoeff = (walkDirection:Dot(testData['Vector']) + 1) / 2
1215 local distanceCoeff = testData['Distance'] / TestDistance
1216 testData["Value"] = directionCoeff * distanceCoeff
1217 end
1218 end
1219
1220 for i, vec in pairs(TestVectors) do
1221 local hitPart, hitPos = Utility.Raycast(Ray.new(currentPoint, vec * TestDistance), true, {Player.Character})
1222 if hitPos then
1223 table.insert(testResults, {Vector = vec; Distance = (hitPos - currentPoint).magnitude})
1224 else
1225 table.insert(testResults, {Vector = vec; Distance = TestDistance})
1226 end
1227 end
1228
1229 ExploreHeuristic()
1230
1231 table.sort(testResults, function(a,b) return a["Value"] > b["Value"] end)
1232
1233 return testResults
1234end
1235
1236local TapId = 1
1237local ExistingPather = nil
1238local ExistingIndicator = nil
1239local PathCompleteListener = nil
1240local PathFailedListener = nil
1241
1242local function CleanupPath()
1243 DrivingTo = nil
1244 if ExistingPather then
1245 ExistingPather:Cancel()
1246 end
1247 if PathCompleteListener then
1248 PathCompleteListener:disconnect()
1249 PathCompleteListener = nil
1250 end
1251 if PathFailedListener then
1252 PathFailedListener:disconnect()
1253 PathFailedListener = nil
1254 end
1255 if ExistingIndicator then
1256 DebrisService:AddItem(ExistingIndicator, 0)
1257 ExistingIndicator = nil
1258 end
1259end
1260
1261local function getExtentsSize(Parts)
1262 local maxX = Parts[1].Position.X
1263 local maxY = Parts[1].Position.Y
1264 local maxZ = Parts[1].Position.Z
1265 local minX = Parts[1].Position.X
1266 local minY = Parts[1].Position.Y
1267 local minZ = Parts[1].Position.Z
1268 for i = 2, #Parts do
1269 maxX = math_max(maxX, Parts[i].Position.X)
1270 maxY = math_max(maxY, Parts[i].Position.Y)
1271 maxZ = math_max(maxZ, Parts[i].Position.Z)
1272 minX = math_min(minX, Parts[i].Position.X)
1273 minY = math_min(minY, Parts[i].Position.Y)
1274 minZ = math_min(minZ, Parts[i].Position.Z)
1275 end
1276 return Region3.new(Vector3_new(minX, minY, minZ), Vector3_new(maxX, maxY, maxZ))
1277end
1278
1279local function inExtents(Extents, Position)
1280 if Position.X < (Extents.CFrame.p.X - Extents.Size.X/2) or Position.X > (Extents.CFrame.p.X + Extents.Size.X/2) then
1281 return false
1282 end
1283 if Position.Z < (Extents.CFrame.p.Z - Extents.Size.Z/2) or Position.Z > (Extents.CFrame.p.Z + Extents.Size.Z/2) then
1284 return false
1285 end
1286 --ignoring Y for now
1287 return true
1288end
1289
1290local AutoJumperInstance = nil
1291local ShootCount = 0
1292local FailCount = 0
1293local function OnTap(tapPositions, goToPoint)
1294 -- Good to remember if this is the latest tap event
1295 TapId = TapId + 1
1296 local thisTapId = TapId
1297
1298
1299 local camera = workspace.CurrentCamera
1300 local character = Player.Character
1301
1302
1303 if not CheckAlive(character) then return end
1304
1305 -- This is a path tap position
1306 if #tapPositions == 1 or goToPoint then
1307 if camera then
1308 local unitRay = Utility.GetUnitRay(tapPositions[1].x, tapPositions[1].y, MyMouse.ViewSizeX, MyMouse.ViewSizeY, camera)
1309 local ray = Ray.new(unitRay.Origin, unitRay.Direction*400)
1310 local hitPart, hitPt = Utility.Raycast(ray, true, {character})
1311
1312 local hitChar, hitHumanoid = Utility.FindChacterAncestor(hitPart)
1313 local torso = character and character:FindFirstChild("Humanoid") and character:FindFirstChild("Humanoid").Torso
1314 local startPos = torso.CFrame.p
1315 if goToPoint then
1316 hitPt = goToPoint
1317 hitChar = nil
1318 end
1319 if hitChar and hitHumanoid and hitHumanoid.Torso and (hitHumanoid.Torso.CFrame.p - torso.CFrame.p).magnitude < 7 then
1320 CleanupPath()
1321
1322 local myHumanoid = findPlayerHumanoid(Player)
1323 if myHumanoid then
1324 myHumanoid:MoveTo(hitPt)
1325 end
1326
1327 ShootCount = ShootCount + 1
1328 local thisShoot = ShootCount
1329 -- Do shooot
1330 local currentWeapon = GetEquippedTool(character)
1331 if currentWeapon then
1332 currentWeapon:Activate()
1333 LastFired = tick()
1334 end
1335 elseif hitPt and character and not CurrentSeatPart then
1336 local thisPather = Pather(character, hitPt)
1337 if thisPather:IsValidPath() then
1338 FailCount = 0
1339 -- TODO: Remove when bug in engine is fixed
1340 Player:Move(Vector3_new(1, 0, 0))
1341 Player:Move(ZERO_VECTOR3)
1342 thisPather:Start()
1343 if BindableEvent_OnFailStateChanged then
1344 BindableEvent_OnFailStateChanged:Fire(false)
1345 end
1346 CleanupPath()
1347
1348 local destinationGlobe = CreateDestinationIndicator(hitPt)
1349 destinationGlobe.Parent = camera
1350
1351 ExistingPather = thisPather
1352 ExistingIndicator = destinationGlobe
1353
1354 if AutoJumperInstance then
1355 AutoJumperInstance:Run()
1356 end
1357
1358 PathCompleteListener = thisPather.Finished:connect(function()
1359 if AutoJumperInstance then
1360 AutoJumperInstance:Stop()
1361 end
1362 if destinationGlobe then
1363 if ExistingIndicator == destinationGlobe then
1364 ExistingIndicator = nil
1365 end
1366 DebrisService:AddItem(destinationGlobe, 0)
1367 destinationGlobe = nil
1368 end
1369 if hitChar then
1370 local humanoid = findPlayerHumanoid(Player)
1371 ShootCount = ShootCount + 1
1372 local thisShoot = ShootCount
1373 -- Do shoot
1374 local currentWeapon = GetEquippedTool(character)
1375 if currentWeapon then
1376 currentWeapon:Activate()
1377 LastFired = tick()
1378 end
1379 if humanoid then
1380 humanoid:MoveTo(hitPt)
1381 end
1382 end
1383 local finishPos = torso and torso.CFrame.p --hitPt
1384 if finishPos and startPos and tick() - Utility.GetLastInput() > 2 then
1385 local exploreResults = ExploreWithRayCast(finishPos, ((startPos - finishPos) * XZ_VECTOR3).unit)
1386 -- Check for Nans etc..
1387 if exploreResults[1] and exploreResults[1]["Vector"] and exploreResults[1]["Vector"].magnitude >= 0.5 and exploreResults[1]["Distance"] > 3 then
1388 if CameraModule then
1389 local rotatedCFrame = CameraModule:LookAtPreserveHeight(finishPos + exploreResults[1]["Vector"] * exploreResults[1]["Distance"])
1390 local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame)
1391 end
1392 end
1393 end
1394 end)
1395 PathFailedListener = thisPather.PathFailed:connect(function()
1396 if AutoJumperInstance then
1397 AutoJumperInstance:Stop()
1398 end
1399 if destinationGlobe then
1400 FlashRed(destinationGlobe)
1401 DebrisService:AddItem(destinationGlobe, 3)
1402 end
1403 end)
1404 else
1405 if hitPt then
1406 -- Feedback here for when we don't have a good path
1407 local failedGlobe = CreateDestinationIndicator(hitPt)
1408 FlashRed(failedGlobe)
1409 DebrisService:AddItem(failedGlobe, 1)
1410 failedGlobe.Parent = camera
1411 if ExistingIndicator == nil then
1412 FailCount = FailCount + 1
1413 if FailCount >= 3 then
1414 if BindableEvent_OnFailStateChanged then
1415 BindableEvent_OnFailStateChanged:Fire(true)
1416 end
1417 CleanupPath()
1418 end
1419 end
1420 end
1421 end
1422 elseif hitPt and character and CurrentSeatPart then
1423 local destinationGlobe = CreateDestinationIndicator(hitPt)
1424 destinationGlobe.Parent = camera
1425 ExistingIndicator = destinationGlobe
1426 DrivingTo = hitPt
1427 local ConnectedParts = CurrentSeatPart:GetConnectedParts(true)
1428
1429 while wait() do
1430 if CurrentSeatPart and ExistingIndicator == destinationGlobe then
1431 local ExtentsSize = getExtentsSize(ConnectedParts)
1432 if inExtents(ExtentsSize, destinationGlobe.Position) then
1433 DebrisService:AddItem(destinationGlobe, 0)
1434 destinationGlobe = nil
1435 DrivingTo = nil
1436 break
1437 end
1438 else
1439 DebrisService:AddItem(destinationGlobe, 0)
1440 if CurrentSeatPart == nil and destinationGlobe == ExistingIndicator then
1441 DrivingTo = nil
1442 OnTap(tapPositions, hitPt)
1443 end
1444 destinationGlobe = nil
1445 break
1446 end
1447 end
1448
1449 else
1450 -- no hit pt
1451 end
1452 end
1453 elseif #tapPositions >= 2 then
1454 if camera then
1455 ShootCount = ShootCount + 1
1456 local thisShoot = ShootCount
1457 -- Do shoot
1458 local avgPoint = Utility.AveragePoints(tapPositions)
1459 local unitRay = Utility.GetUnitRay(avgPoint.x, avgPoint.y, MyMouse.ViewSizeX, MyMouse.ViewSizeY, camera)
1460 local currentWeapon = GetEquippedTool(character)
1461 if currentWeapon then
1462 currentWeapon:Activate()
1463 LastFired = tick()
1464 end
1465 end
1466 end
1467end
1468
1469
1470local function CreateClickToMoveModule()
1471 local this = {}
1472
1473 local LastStateChange = 0
1474 local LastState = Enum.HumanoidStateType.Running
1475 local FingerTouches = {}
1476 local NumUnsunkTouches = 0
1477 -- PC simulation
1478 local mouse1Down = tick()
1479 local mouse1DownPos = Vector2.new()
1480 local mouse2Down = tick()
1481 local mouse2DownPos = Vector2.new()
1482 local mouse2Up = tick()
1483
1484 local movementKeys = {
1485 [Enum.KeyCode.W] = true;
1486 [Enum.KeyCode.A] = true;
1487 [Enum.KeyCode.S] = true;
1488 [Enum.KeyCode.D] = true;
1489 [Enum.KeyCode.Up] = true;
1490 [Enum.KeyCode.Down] = true;
1491 }
1492
1493 local TapConn = nil
1494 local InputBeganConn = nil
1495 local InputChangedConn = nil
1496 local InputEndedConn = nil
1497 local HumanoidDiedConn = nil
1498 local CharacterChildAddedConn = nil
1499 local OnCharacterAddedConn = nil
1500 local CharacterChildRemovedConn = nil
1501 local RenderSteppedConn = nil
1502 local HumanoidSeatedConn = nil
1503
1504 local function disconnectEvent(event)
1505 if event then
1506 event:disconnect()
1507 end
1508 end
1509
1510 local function DisconnectEvents()
1511 disconnectEvent(TapConn)
1512 disconnectEvent(InputBeganConn)
1513 disconnectEvent(InputChangedConn)
1514 disconnectEvent(InputEndedConn)
1515 disconnectEvent(HumanoidDiedConn)
1516 disconnectEvent(CharacterChildAddedConn)
1517 disconnectEvent(OnCharacterAddedConn)
1518 disconnectEvent(RenderSteppedConn)
1519 disconnectEvent(CharacterChildRemovedConn)
1520 pcall(function() RunService:UnbindFromRenderStep("ClickToMoveRenderUpdate") end)
1521 disconnectEvent(HumanoidSeatedConn)
1522 end
1523
1524
1525
1526 local function IsFinite(num)
1527 return num == num and num ~= 1/0 and num ~= -1/0
1528 end
1529
1530 local function findAngleBetweenXZVectors(vec2, vec1)
1531 return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
1532 end
1533
1534 -- Setup the camera
1535 CameraModule = ClassicCameraModule()
1536
1537 do
1538 -- Extend The Camera Module Class
1539 function CameraModule:LookAtPreserveHeight(newLookAtPt)
1540 local camera = workspace.CurrentCamera
1541
1542 local focus = camera.Focus.p
1543
1544 local cameraCFrame = CameraModule.cframe
1545 local mag = Vector3_new(cameraCFrame.lookVector.x, 0, cameraCFrame.lookVector.z).magnitude
1546 local newLook = (Vector3_new(newLookAtPt.x, focus.y, newLookAtPt.z) - focus).unit * mag
1547 local flippedLook = newLook + Vector3_new(0, cameraCFrame.lookVector.y, 0)
1548
1549 local distance = (focus - cameraCFrame.p).magnitude
1550
1551 local newCamPos = focus - flippedLook.unit * distance
1552 return CFrame.new(newCamPos, newCamPos + flippedLook)
1553 end
1554
1555 local lerp = CFrame.new().lerp
1556 function CameraModule:TweenCameraLook(desiredCFrame, speed)
1557 local e = 2.718281828459
1558
1559 local function SCurve(t)
1560 return 1/(1 + e^(-t*1.5))
1561 end
1562
1563 local function easeOutSine(t, b, c, d)
1564 if t >= d then return b + c end
1565 return c * math_sin(t/d * (math_pi/2)) + b;
1566 end
1567
1568 local c0, c1 = CFrame_new(ZERO_VECTOR3, self:GetCameraLook()), desiredCFrame - desiredCFrame.p
1569 local theta = GetThetaBetweenCFrames(c0, c1)
1570 theta = clamp(0, math_pi, theta)
1571 local duration = 0.65 * SCurve(theta - math_pi/4) + 0.15
1572 if speed then
1573 duration = theta / speed
1574 end
1575 local start = tick()
1576 local finish = start + duration
1577
1578 self.UpdateTweenFunction = function()
1579 local currTime = tick() - start
1580 local alpha = clamp(0, 1, easeOutSine(currTime, 0, 1, duration))
1581 local newCFrame = lerp(c0, c1, alpha)
1582 local y = findAngleBetweenXZVectors(newCFrame.lookVector, self:GetCameraLook())
1583 if IsFinite(y) and math_abs(y) > 0.0001 then
1584 self.RotateInput = self.RotateInput + Vector2_new(y, 0)
1585 end
1586 return (currTime >= finish or alpha >= 1)
1587 end
1588 end
1589 end
1590 --- Done Extending
1591
1592
1593 local function OnTouchBegan(input, processed)
1594 if FingerTouches[input] == nil and not processed then
1595 NumUnsunkTouches = NumUnsunkTouches + 1
1596 end
1597 FingerTouches[input] = processed
1598 end
1599
1600 local function OnTouchChanged(input, processed)
1601 if FingerTouches[input] == nil then
1602 FingerTouches[input] = processed
1603 if not processed then
1604 NumUnsunkTouches = NumUnsunkTouches + 1
1605 end
1606 end
1607 end
1608
1609 local function OnTouchEnded(input, processed)
1610 --print("Touch tap fake:" , processed)
1611 --if not processed then
1612 -- OnTap({input.Position})
1613 --end
1614 if FingerTouches[input] ~= nil and FingerTouches[input] == false then
1615 NumUnsunkTouches = NumUnsunkTouches - 1
1616 end
1617 FingerTouches[input] = nil
1618 end
1619
1620
1621 local function OnCharacterAdded(character)
1622 DisconnectEvents()
1623
1624 InputBeganConn = UIS.InputBegan:connect(function(input, processed)
1625 if input.UserInputType == Enum.UserInputType.Touch then
1626 OnTouchBegan(input, processed)
1627
1628
1629 -- Give back controls when they tap both sticks
1630 local wasInBottomLeft = IsInBottomLeft(input.Position)
1631 local wasInBottomRight = IsInBottomRight(input.Position)
1632 if wasInBottomRight or wasInBottomLeft then
1633 for otherInput, _ in pairs(FingerTouches) do
1634 if otherInput ~= input then
1635 local otherInputInLeft = IsInBottomLeft(otherInput.Position)
1636 local otherInputInRight = IsInBottomRight(otherInput.Position)
1637 if otherInput.UserInputState ~= Enum.UserInputState.End and ((wasInBottomLeft and otherInputInRight) or (wasInBottomRight and otherInputInLeft)) then
1638 if BindableEvent_OnFailStateChanged then
1639 BindableEvent_OnFailStateChanged:Fire(true)
1640 end
1641 return
1642 end
1643 end
1644 end
1645 end
1646 end
1647
1648 -- Cancel path when you use the keyboard controls.
1649 if processed == false and input.UserInputType == Enum.UserInputType.Keyboard and movementKeys[input.KeyCode] then
1650 CleanupPath()
1651 end
1652 if input.UserInputType == Enum.UserInputType.MouseButton1 then
1653 mouse1Down = tick()
1654 mouse1DownPos = input.Position
1655 end
1656 if input.UserInputType == Enum.UserInputType.MouseButton2 then
1657 mouse2Down = tick()
1658 mouse2DownPos = input.Position
1659 end
1660 end)
1661
1662 InputChangedConn = UIS.InputChanged:connect(function(input, processed)
1663 if input.UserInputType == Enum.UserInputType.Touch then
1664 OnTouchChanged(input, processed)
1665 end
1666 end)
1667
1668 InputEndedConn = UIS.InputEnded:connect(function(input, processed)
1669 if input.UserInputType == Enum.UserInputType.Touch then
1670 OnTouchEnded(input, processed)
1671 end
1672
1673 if input.UserInputType == Enum.UserInputType.MouseButton2 then
1674 mouse2Up = tick()
1675 local currPos = input.Position
1676 if mouse2Up - mouse2Down < 0.25 and (currPos - mouse2DownPos).magnitude < 5 then
1677 local positions = {currPos}
1678 OnTap(positions)
1679 end
1680 end
1681 end)
1682
1683 TapConn = UIS.TouchTap:connect(function(touchPositions, processed)
1684 if not processed then
1685 OnTap(touchPositions)
1686 end
1687 end)
1688
1689 if not UIS.TouchEnabled then -- PC
1690 if AutoJumperInstance then
1691 AutoJumperInstance:Stop()
1692 AutoJumperInstance = nil
1693 end
1694 AutoJumperInstance = AutoJumper()
1695 end
1696
1697 local function getThrottleAndSteer(object, point)
1698 local lookVector = (point - object.Position)
1699 lookVector = Vector3_new(lookVector.X, 0, lookVector.Z).unit
1700 local objectVector = Vector3_new(object.CFrame.lookVector.X, 0, object.CFrame.lookVector.Z).unit
1701 local dirVector = lookVector - objectVector
1702 local mag = dirVector.magnitude
1703 local degrees = math_deg(math_acos(lookVector:Dot(objectVector)))
1704 local side = (object.CFrame:pointToObjectSpace(point).X > 0)
1705 local throttle = 0
1706 if mag < 0.25 then
1707 throttle = 1
1708 end
1709 if mag > 1.8 then
1710 throttle = -1
1711 end
1712 local distance = CurrentSeatPart.Position - DrivingTo
1713 local velocity = CurrentSeatPart.Velocity
1714 if velocity.magnitude*1.5 > distance.magnitude then
1715 if velocity.magnitude*0.5 > distance.magnitude then
1716 throttle = -throttle
1717 else
1718 throttle = 0
1719 end
1720 end
1721 local steer = 0
1722 if degrees > 5 and degrees < 175 then
1723 if side then
1724 steer = 1
1725 else
1726 steer = -1
1727 end
1728 end
1729 local rotatingAt = math_deg(CurrentSeatPart.RotVelocity.magnitude)
1730 local degreesAway = math_max(math_min(degrees, 180 - degrees), 10)
1731 if (CurrentSeatPart.RotVelocity.X < 0)== (steer < 0) then
1732 if rotatingAt*1.5 > degreesAway then
1733 if rotatingAt*0.5 > degreesAway then
1734 steer = -steer
1735 else
1736 steer = 0
1737 end
1738 end
1739 end
1740 return throttle, steer
1741 end
1742
1743 local function Update()
1744 if CameraModule then
1745 if CameraModule.UserPanningTheCamera then
1746 CameraModule.UpdateTweenFunction = nil
1747 else
1748 if CameraModule.UpdateTweenFunction then
1749 local done = CameraModule.UpdateTweenFunction()
1750 if done then
1751 CameraModule.UpdateTweenFunction = nil
1752 end
1753 end
1754 end
1755 CameraModule:Update()
1756 end
1757
1758 if CurrentSeatPart then
1759 if DrivingTo then
1760 local throttle, steer = getThrottleAndSteer(CurrentSeatPart, DrivingTo)
1761 CurrentSeatPart.Throttle = throttle
1762 CurrentSeatPart.Steer = steer
1763 end
1764 end
1765 end
1766
1767 local success = pcall(function() RunService:BindToRenderStep("ClickToMoveRenderUpdate",Enum.RenderPriority.Camera.Value - 1,Update) end)
1768 if not success then
1769 if RenderSteppedConn then
1770 RenderSteppedConn:disconnect()
1771 end
1772 RenderSteppedConn = RunService.RenderStepped:connect(Update)
1773 end
1774
1775 local WasAutoJumper = false
1776 local WasAutoJumpMobile = false
1777 local function onSeated(child, active, currentSeatPart)
1778 if active then
1779 if BindableEvent_EnableTouchJump then
1780 BindableEvent_EnableTouchJump:Fire(true)
1781 end
1782 if currentSeatPart and currentSeatPart.ClassName == "VehicleSeat" then
1783 CurrentSeatPart = currentSeatPart
1784 if AutoJumperInstance then
1785 AutoJumperInstance:Stop()
1786 AutoJumperInstance = nil
1787 WasAutoJumper = true
1788 else
1789 WasAutoJumper = false
1790 end
1791 if child.AutoJumpEnabled then
1792 WasAutoJumpMobile = true
1793 child.AutoJumpEnabled = false
1794 end
1795 end
1796 else
1797 CurrentSeatPart = nil
1798 if BindableEvent_EnableTouchJump then
1799 BindableEvent_EnableTouchJump:Fire(false)
1800 end
1801 if WasAutoJumper then
1802 AutoJumperInstance = AutoJumper()
1803 WasAutoJumper = false
1804 end
1805 if WasAutoJumpMobile then
1806 child.AutoJumpEnabled = true
1807 WasAutoJumpMobile = false
1808 end
1809 end
1810 end
1811
1812 local function OnCharacterChildAdded(child)
1813 if UIS.TouchEnabled then
1814 if child:IsA('Tool') then
1815 child.ManualActivationOnly = true
1816 end
1817 end
1818 if child:IsA('Humanoid') then
1819 disconnectEvent(HumanoidDiedConn)
1820 HumanoidDiedConn = child.Died:connect(function()
1821 DebrisService:AddItem(ExistingIndicator, 1)
1822 if AutoJumperInstance then
1823 AutoJumperInstance:Stop()
1824 AutoJumperInstance = nil
1825 end
1826 end)
1827 local WasAutoJumper = false
1828 local WasAutoJumpMobile = false
1829 HumanoidSeatedConn = child.Seated:connect(function(active, seat) onSeated(child, active, seat) end)
1830 if child.SeatPart then
1831 onSeated(child, true, child.SeatPart)
1832 end
1833 end
1834 end
1835
1836 CharacterChildAddedConn = character.ChildAdded:connect(function(child)
1837 OnCharacterChildAdded(child)
1838 end)
1839 CharacterChildRemovedConn = character.ChildRemoved:connect(function(child)
1840 if UIS.TouchEnabled then
1841 if child:IsA('Tool') then
1842 child.ManualActivationOnly = false
1843 end
1844 end
1845 end)
1846 for _, child in pairs(character:GetChildren()) do
1847 OnCharacterChildAdded(child)
1848 end
1849 end
1850
1851 local Running = false
1852
1853 function this:Stop()
1854 if Running then
1855 DisconnectEvents()
1856 CleanupPath()
1857 if AutoJumperInstance then
1858 AutoJumperInstance:Stop()
1859 AutoJumperInstance = nil
1860 end
1861 if CameraModule then
1862 CameraModule.UpdateTweenFunction = nil
1863 CameraModule:SetEnabled(false)
1864 end
1865 -- Restore tool activation on shutdown
1866 if UIS.TouchEnabled then
1867 local character = Player.Character
1868 if character then
1869 for _, child in pairs(character:GetChildren()) do
1870 if child:IsA('Tool') then
1871 child.ManualActivationOnly = false
1872 end
1873 end
1874 end
1875 end
1876 DrivingTo = nil
1877 Running = false
1878 end
1879 end
1880
1881 function this:Start()
1882 if not Running then
1883 if Player.Character then -- retro-listen
1884 OnCharacterAdded(Player.Character)
1885 end
1886 OnCharacterAddedConn = Player.CharacterAdded:connect(OnCharacterAdded)
1887 if CameraModule then
1888 CameraModule:SetEnabled(true)
1889 end
1890 Running = true
1891 end
1892 end
1893
1894 return this
1895end
1896
1897return CreateClickToMoveModule
1898
1899-- Written By Kip Turner, Copyright Roblox 2014
1900-- Updated by Garnold to utilize NavigationService, 2017
1901
1902local UIS = game:GetService("UserInputService")
1903local NavigationService = game:GetService("NavigationService")
1904local PlayerService = game:GetService("Players")
1905local RunService = game:GetService("RunService")
1906local DebrisService = game:GetService('Debris')
1907local ReplicatedStorage = game:GetService('ReplicatedStorage')
1908local tweenService = game:GetService("TweenService")
1909
1910local CameraScript = script:FindFirstAncestor("CameraScript")
1911local InvisicamModule = require(CameraScript:WaitForChild("Invisicam"))
1912local OrbitalCamModule = require(CameraScript:WaitForChild('RootCamera'):WaitForChild('OrbitalCamera'))
1913
1914local Player = PlayerService.LocalPlayer
1915local PlayerScripts = Player.PlayerScripts
1916
1917local ControlScript = PlayerScripts:FindFirstChild("ControlScript")
1918local MasterControl, TouchJump
1919if ControlScript then
1920 MasterControl = ControlScript:FindFirstChild("MasterControl")
1921 if MasterControl then
1922 local TouchJumpModule = MasterControl:FindFirstChild("TouchJump")
1923 if TouchJumpModule then
1924 TouchJump = require(TouchJumpModule)
1925 end
1926 end
1927end
1928
1929local SHOW_PATH = true
1930
1931local RayCastIgnoreList = workspace.FindPartOnRayWithIgnoreList
1932
1933local math_min = math.min
1934local math_max = math.max
1935local math_pi = math.pi
1936local math_atan2 = math.atan2
1937
1938local Vector3_new = Vector3.new
1939local Vector2_new = Vector2.new
1940local CFrame_new = CFrame.new
1941
1942local CurrentSeatPart = nil
1943local DrivingTo = nil
1944
1945local XZ_VECTOR3 = Vector3_new(1, 0, 1)
1946local ZERO_VECTOR3 = Vector3_new(0, 0, 0)
1947local ZERO_VECTOR2 = Vector2_new(0, 0)
1948
1949local lastFailedPosition = nil
1950
1951local BindableEvent_OnFailStateChanged = nil
1952if UIS.TouchEnabled then
1953 BindableEvent_OnFailStateChanged = Instance.new('BindableEvent')
1954 BindableEvent_OnFailStateChanged.Name = "OnClickToMoveFailStateChange"
1955 BindableEvent_OnFailStateChanged.Parent = PlayerScripts
1956end
1957
1958--------------------------UTIL LIBRARY-------------------------------
1959local Utility = {}
1960do
1961 local Signal = {}
1962 function Signal.Create()
1963 local sig = {}
1964
1965 local mSignaler = Instance.new('BindableEvent')
1966
1967 local mArgData = nil
1968 local mArgDataCount = nil
1969
1970 function sig:fire(...)
1971 mArgData = {...}
1972 mArgDataCount = select('#', ...)
1973 mSignaler:Fire()
1974 end
1975
1976 function sig:connect(f)
1977 if not f then error("connect(nil)", 2) end
1978 return mSignaler.Event:connect(function()
1979 f(unpack(mArgData, 1, mArgDataCount))
1980 end)
1981 end
1982
1983 function sig:wait()
1984 mSignaler.Event:wait()
1985 assert(mArgData, "Missing arg data, likely due to :TweenSize/Position corrupting threadrefs.")
1986 return unpack(mArgData, 1, mArgDataCount)
1987 end
1988
1989 return sig
1990 end
1991 Utility.Signal = Signal
1992
1993 function Utility.Create(instanceType)
1994 return function(data)
1995 local obj = Instance.new(instanceType)
1996 for k, v in pairs(data) do
1997 if type(k) == 'number' then
1998 v.Parent = obj
1999 else
2000 obj[k] = v
2001 end
2002 end
2003 return obj
2004 end
2005 end
2006
2007 local function ViewSizeX()
2008 local camera = workspace.CurrentCamera
2009 local x = camera and camera.ViewportSize.X or 0
2010 local y = camera and camera.ViewportSize.Y or 0
2011 if x == 0 then
2012 return 1024
2013 else
2014 if x > y then
2015 return x
2016 else
2017 return y
2018 end
2019 end
2020 end
2021 Utility.ViewSizeX = ViewSizeX
2022
2023 local function ViewSizeY()
2024 local camera = workspace.CurrentCamera
2025 local x = camera and camera.ViewportSize.X or 0
2026 local y = camera and camera.ViewportSize.Y or 0
2027 if y == 0 then
2028 return 768
2029 else
2030 if x > y then
2031 return y
2032 else
2033 return x
2034 end
2035 end
2036 end
2037 Utility.ViewSizeY = ViewSizeY
2038
2039 local function FindChacterAncestor(part)
2040 if part then
2041 local humanoid = part:FindFirstChild("Humanoid")
2042 if humanoid then
2043 return part, humanoid
2044 else
2045 return FindChacterAncestor(part.Parent)
2046 end
2047 end
2048 end
2049 Utility.FindChacterAncestor = FindChacterAncestor
2050
2051 local function Raycast(ray, ignoreNonCollidable, ignoreList)
2052 local ignoreList = ignoreList or {}
2053 local hitPart, hitPos, hitNorm, hitMat = RayCastIgnoreList(workspace, ray, ignoreList)
2054 if hitPart then
2055 if ignoreNonCollidable and hitPart.CanCollide == false then
2056 table.insert(ignoreList, hitPart)
2057 return Raycast(ray, ignoreNonCollidable, ignoreList)
2058 end
2059 return hitPart, hitPos, hitNorm, hitMat
2060 end
2061 return nil, nil
2062 end
2063 Utility.Raycast = Raycast
2064
2065 local function AveragePoints(positions)
2066 local avgPos = ZERO_VECTOR2
2067 if #positions > 0 then
2068 for i = 1, #positions do
2069 avgPos = avgPos + positions[i]
2070 end
2071 avgPos = avgPos / #positions
2072 end
2073 return avgPos
2074 end
2075 Utility.AveragePoints = AveragePoints
2076
2077 local function FuzzyEquals(numa, numb)
2078 return numa + 0.1 > numb and numa - 0.1 < numb
2079 end
2080 Utility.FuzzyEquals = FuzzyEquals
2081
2082 local LastInput = 0
2083 UIS.InputBegan:connect(function(inputObject, wasSunk)
2084 if not wasSunk then
2085 if inputObject.UserInputType == Enum.UserInputType.Touch or
2086 inputObject.UserInputType == Enum.UserInputType.MouseButton1 or
2087 inputObject.UserInputType == Enum.UserInputType.MouseButton2 then
2088 LastInput = tick()
2089 end
2090 end
2091 end)
2092 Utility.GetLastInput = function()
2093 return LastInput
2094 end
2095end
2096
2097local humanoidCache = {}
2098local function findPlayerHumanoid(player)
2099 local character = player and player.Character
2100 if character then
2101 local resultHumanoid = humanoidCache[player]
2102 if resultHumanoid and resultHumanoid.Parent == character then
2103 return resultHumanoid
2104 else
2105 humanoidCache[player] = nil -- Bust Old Cache
2106 local humanoid = character:FindFirstChildOfClass("Humanoid")
2107 if humanoid then
2108 humanoidCache[player] = humanoid
2109 end
2110 return humanoid
2111 end
2112 end
2113end
2114
2115---------------------------------------------------------
2116
2117local Signal = Utility.Signal
2118local Create = Utility.Create
2119
2120--------------------------CHARACTER CONTROL-------------------------------
2121local CurrentIgnoreList
2122
2123local function GetCharacter()
2124 return Player and Player.Character
2125end
2126
2127local function GetTorso()
2128 local humanoid = findPlayerHumanoid(Player)
2129 return humanoid and humanoid.Torso
2130end
2131
2132local function getIgnoreList()
2133 if CurrentIgnoreList then
2134 return CurrentIgnoreList
2135 end
2136 CurrentIgnoreList = {}
2137 table.insert(CurrentIgnoreList, GetCharacter())
2138 return CurrentIgnoreList
2139end
2140
2141-----------------------------------------------------------------------------
2142
2143-----------------------------------PATHER--------------------------------------
2144
2145local function createNewPopup(popupType)
2146
2147 local newModel = Instance.new("ImageHandleAdornment")
2148
2149 newModel.AlwaysOnTop = false
2150 newModel.Image = "rbxasset://textures/Cursors/Gamepad/Pointer@2x.png"
2151 newModel.ZIndex = 2
2152
2153 local size = ZERO_VECTOR2
2154 if popupType == "DestinationPopup" then
2155 newModel.Color3 = Color3.fromRGB(0, 175, 255)
2156 size = Vector2.new(4,4)
2157 elseif popupType == "DirectWalkPopup" then
2158 newModel.Color3 = Color3.fromRGB(255, 255, 100)
2159 size = Vector2.new(4,4)
2160 elseif popupType == "FailurePopup" then
2161 newModel.Color3 = Color3.fromRGB(255, 100, 100)
2162 size = Vector2.new(4,4)
2163 elseif popupType == "PatherPopup" then
2164 newModel.Color3 = Color3.fromRGB(255, 255, 255)
2165 size = Vector2.new(3,3)
2166 newModel.ZIndex = 1
2167 end
2168
2169 local dataStructure = {}
2170 dataStructure.Model = newModel
2171
2172 function dataStructure:TweenIn()
2173 local tween1 = tweenService:Create(self.Model,
2174 TweenInfo.new(
2175 1,
2176 Enum.EasingStyle.Elastic,
2177 Enum.EasingDirection.Out,
2178 0,
2179 false,
2180 0
2181 ),{
2182 Size = size
2183 }
2184 )
2185 tween1:Play()
2186 return tween1
2187 end
2188
2189 function dataStructure:TweenOut()
2190 local tween1 = tweenService:Create(self.Model,
2191 TweenInfo.new(
2192 .25,
2193 Enum.EasingStyle.Quad,
2194 Enum.EasingDirection.In,
2195 0,
2196 false,
2197 0
2198 ),{
2199 Size = ZERO_VECTOR2
2200 }
2201 )
2202 tween1:Play()
2203 return tween1
2204 end
2205
2206 function dataStructure:Place(position, dest)
2207 -- place the model at position
2208 if not self.Model.Parent then
2209 self.Model.Parent = workspace.Terrain
2210 self.Model.Adornee = workspace.Terrain
2211 self.Model.CFrame = CFrame.new(position,dest)*CFrame.Angles(math.pi/2,0,0)-Vector3.new(0,-.2,0)
2212 end
2213 end
2214
2215 return dataStructure
2216end
2217
2218local function createPopupPath(points, numCircles)
2219 -- creates a path with the provided points, using the path and number of circles provided
2220 local popups = {}
2221 local stopTraversing = false
2222
2223 local function killPopup(i)
2224 -- kill all popups before and at i
2225 for iter, v in pairs(popups) do
2226 if iter <= i then
2227 local tween = v:TweenOut()
2228 spawn(function()
2229 tween.Completed:wait()
2230 v.Model:Destroy()
2231 end)
2232 popups[iter] = nil
2233 end
2234 end
2235 end
2236
2237 local function stopFunction()
2238 stopTraversing = true
2239 killPopup(#points)
2240 end
2241
2242 spawn(function()
2243 for i = 1, #points do
2244 if stopTraversing then
2245 break
2246 end
2247 if i%numCircles == 0 then
2248 local popup = createNewPopup("PatherPopup")
2249 popups[i] = popup
2250 local nextPopup = points[i+1]
2251 popup:Place(points[i].Position, nextPopup and nextPopup.Position or points[#points].Position)
2252 local tween = popup:TweenIn()
2253 wait(0.2)
2254 end
2255 end
2256 end)
2257
2258 return stopFunction, killPopup
2259end
2260
2261local function createTrailPath(points, width, speed) -- list of points, speed in studs/second, returns a function to stop the pathfinding
2262 local stopTraversing = false
2263 local currentPoint = 1
2264 local setCurrentPoint = function(point)
2265 currentPoint = point
2266 end
2267 local stopFunction = function()
2268 stopTraversing = true
2269 end
2270 spawn(function()
2271 local attachments = {Instance.new("Attachment",workspace.Terrain), Instance.new("Attachment",workspace.Terrain)}
2272 local trail = Create'Trail'
2273 {
2274 Parent = workspace.CurrentCamera,
2275 Transparency = NumberSequence.new(0.5,1),
2276 LightEmission = 1,
2277 Lifetime = .1,
2278 Attachment0 = attachments[1],
2279 Attachment1 = attachments[2]
2280 }
2281
2282 local startTime = tick()
2283
2284 while not stopTraversing do
2285 for i = currentPoint, #points do
2286 if i%speed == 0 or i == #points then
2287 if stopTraversing then
2288 break
2289 end
2290 local pos = points[i].Position
2291 local nextPos = points[i+1] and points[i+1].Position
2292 if pos and nextPos then
2293 local centerCF = CFrame_new(pos, nextPos)
2294 attachments[1].CFrame = CFrame_new(centerCF:pointToWorldSpace(Vector3_new(-width/2,1,0)), nextPos)
2295 attachments[2].CFrame = CFrame_new(centerCF:pointToWorldSpace(Vector3_new(width/2,1,0)), nextPos)
2296 end
2297 RunService.RenderStepped:wait()
2298 end
2299 end
2300 wait(trail.Lifetime)
2301 trail.Enabled = false
2302 local centerCF = CFrame_new(points[1].Position, points[2].Position)
2303 attachments[1].CFrame = CFrame_new(centerCF:pointToWorldSpace(Vector3_new(-width/2,1,0)), points[2].Position)
2304 attachments[2].CFrame = CFrame_new(centerCF:pointToWorldSpace(Vector3_new(width/2,1,0)), points[2].Position)
2305 wait(1)
2306 trail.Enabled = true
2307 end
2308
2309 trail:Destroy()
2310 attachments[1]:Destroy()
2311 attachments[2]:Destroy()
2312 attachments = nil
2313 end)
2314 return stopFunction, setCurrentPoint
2315end
2316
2317local function Pather(character, endPoint, surfaceNormal)
2318 local this = {}
2319
2320 this.Cancelled = false
2321 this.Started = false
2322
2323 this.Finished = Signal.Create()
2324 this.PathFailed = Signal.Create()
2325 this.PathStarted = Signal.Create()
2326
2327 this.PathComputing = false
2328 this.PathComputed = false
2329
2330 this.TargetPoint = endPoint
2331 this.TargetSurfaceNormal = surfaceNormal
2332
2333 function this:YieldUntilPointReached(character, point, timeout)
2334 timeout = timeout or 10000000 --why
2335
2336 local humanoid = findPlayerHumanoid(Player)
2337 local torso = humanoid and humanoid.Torso
2338 local start = tick()
2339 local lastMoveTo = start
2340 while torso and tick() - start < timeout and this.Cancelled == false do
2341 local diffVector = (point - torso.CFrame.p)
2342 local xzMagnitude = (diffVector * XZ_VECTOR3).magnitude
2343
2344 -- The hard-coded number 2 here is from the engine's MoveTo implementation
2345 if xzMagnitude < 2 then
2346 return true
2347 end
2348 -- Keep on issuing the move command because it will automatically quit every so often.
2349 if tick() - lastMoveTo > 1.5 then
2350 humanoid:MoveTo(point)
2351 lastMoveTo = tick()
2352 end
2353 wait()
2354 end
2355 return false
2356 end
2357
2358 function this:Cancel()
2359 this.Cancelled = true
2360 local humanoid = findPlayerHumanoid(Player)
2361 local torso = humanoid and humanoid.Torso
2362 if humanoid and torso then
2363 humanoid:MoveTo(torso.CFrame.p)
2364 end
2365 end
2366
2367 function this:ComputePath()
2368 local humanoid = findPlayerHumanoid(Player)
2369 local torso = humanoid and humanoid.Torso
2370 local success = false
2371 if torso then
2372 if this.PathComputed or this.PathComputing then return end
2373 this.PathComputing = true
2374 success = pcall(function()
2375 this.pathResult = NavigationService:FindPathAsync(torso.CFrame.p, this.TargetPoint)
2376 end)
2377 this.pointList = this.pathResult and this.pathResult:GetWaypoints()
2378 this.PathComputing = false
2379 this.PathComputed = this.pathResult and this.pathResult.Status == Enum.NavigationStatus.Success or false
2380 end
2381 return true
2382 end
2383
2384 function this:IsValidPath()
2385 if not this.pathResult then
2386 this:ComputePath()
2387 end
2388 return this.pathResult.Status == Enum.NavigationStatus.Success
2389 end
2390
2391 function this:Start()
2392 if CurrentSeatPart then
2393 return
2394 end
2395 spawn(function()
2396 local humanoid = findPlayerHumanoid(Player)
2397 local torso = humanoid and humanoid.Torso
2398 if torso then
2399 if this.Started then return end
2400 this.Started = true
2401 local smoothPath = this.pointList
2402
2403 local stopTraverseFunc = nil
2404 local setPointFunc = nil
2405 if SHOW_PATH then
2406 -- choose whichever one Mike likes best
2407 stopTraverseFunc, setPointFunc = createPopupPath(smoothPath, 30)
2408 --stopTraverseFunc, setPointFunc = createTrailPath(smoothPath, .6, 5)
2409 end
2410
2411 for i = 1, #smoothPath do
2412 if setPointFunc then
2413 setPointFunc(i+2)
2414 end
2415 local point = smoothPath[i]
2416 local pos = point.Position
2417 if humanoid then
2418 if this.Cancelled then
2419 return
2420 end
2421
2422 humanoid:MoveTo(pos)
2423 if point.Action == Enum.NavigationWaypointAction.Jump then
2424 humanoid.Jump = true
2425 end
2426
2427 local distance = ((torso.CFrame.p - pos) * XZ_VECTOR3).magnitude
2428 local approxTime = 10
2429 if humanoid.WalkSpeed > 0 then
2430 approxTime = distance / humanoid.WalkSpeed
2431 end
2432
2433 local didReach = this:YieldUntilPointReached(character, pos, approxTime * 3 + 1)
2434
2435 if not didReach then
2436 if stopTraverseFunc then
2437 stopTraverseFunc()
2438 end
2439 this.PathFailed:fire()
2440 return
2441 end
2442 end
2443 end
2444
2445 if stopTraverseFunc then
2446 stopTraverseFunc()
2447 end
2448 this.Finished:fire()
2449 return
2450 end
2451 this.PathFailed:fire()
2452 end)
2453 end
2454
2455 this:ComputePath()
2456 if not this.PathComputed then
2457 -- set the end point towards the camera and raycasted towards the ground in case we hit a wall
2458 local offsetPoint = this.TargetPoint + this.TargetSurfaceNormal*1.5
2459 local ray = Ray.new(offsetPoint, Vector3_new(0,-1,0)*50)
2460 local newHitPart, newHitPos = RayCastIgnoreList(workspace, ray, getIgnoreList())
2461 if newHitPart then
2462 this.TargetPoint = newHitPos
2463 end
2464 -- try again
2465 this:ComputePath()
2466 end
2467
2468 return this
2469end
2470
2471-------------------------------------------------------------------------
2472
2473local function IsInBottomLeft(pt)
2474 local joystickHeight = math_min(Utility.ViewSizeY() * 0.33, 250)
2475 local joystickWidth = joystickHeight
2476 return pt.X <= joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
2477end
2478
2479local function IsInBottomRight(pt)
2480 local joystickHeight = math_min(Utility.ViewSizeY() * 0.33, 250)
2481 local joystickWidth = joystickHeight
2482 return pt.X >= Utility.ViewSizeX() - joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
2483end
2484
2485local function CheckAlive(character)
2486 local humanoid = findPlayerHumanoid(Player)
2487 return humanoid ~= nil and humanoid.Health > 0
2488end
2489
2490local function GetEquippedTool(character)
2491 if character ~= nil then
2492 for _, child in pairs(character:GetChildren()) do
2493 if child:IsA('Tool') then
2494 return child
2495 end
2496 end
2497 end
2498end
2499
2500local ExistingPather = nil
2501local ExistingIndicator = nil
2502local PathCompleteListener = nil
2503local PathFailedListener = nil
2504
2505local function CleanupPath()
2506 DrivingTo = nil
2507 if ExistingPather then
2508 ExistingPather:Cancel()
2509 end
2510 if PathCompleteListener then
2511 PathCompleteListener:disconnect()
2512 PathCompleteListener = nil
2513 end
2514 if PathFailedListener then
2515 PathFailedListener:disconnect()
2516 PathFailedListener = nil
2517 end
2518 if ExistingIndicator then
2519 local obj = ExistingIndicator
2520 local tween = obj:TweenOut()
2521 local tweenCompleteEvent = nil
2522 tweenCompleteEvent = tween.Completed:connect(function()
2523 tweenCompleteEvent:disconnect()
2524 obj.Model:Destroy()
2525 end)
2526 ExistingIndicator = nil
2527 end
2528end
2529
2530local function getExtentsSize(Parts)
2531 local maxX,maxY,maxZ = -math.huge,-math.huge,-math.huge
2532 local minX,minY,minZ = math.huge,math.huge,math.huge
2533 for i = 1, #Parts do
2534 maxX,maxY,maxZ = math_max(maxX, Parts[i].Position.X), math_max(maxY, Parts[i].Position.Y), math_max(maxZ, Parts[i].Position.Z)
2535 minX,minY,minZ = math_min(minX, Parts[i].Position.X), math_min(minY, Parts[i].Position.Y), math_min(minZ, Parts[i].Position.Z)
2536 end
2537 return Region3.new(Vector3_new(minX, minY, minZ), Vector3_new(maxX, maxY, maxZ))
2538end
2539
2540local function inExtents(Extents, Position)
2541 if Position.X < (Extents.CFrame.p.X - Extents.Size.X/2) or Position.X > (Extents.CFrame.p.X + Extents.Size.X/2) then
2542 return false
2543 end
2544 if Position.Z < (Extents.CFrame.p.Z - Extents.Size.Z/2) or Position.Z > (Extents.CFrame.p.Z + Extents.Size.Z/2) then
2545 return false
2546 end
2547 --ignoring Y for now
2548 return true
2549end
2550
2551local FailCount = 0
2552local function OnTap(tapPositions, goToPoint)
2553 -- Good to remember if this is the latest tap event
2554 local camera = workspace.CurrentCamera
2555 local character = Player.Character
2556
2557 if not CheckAlive(character) then return end
2558
2559 -- This is a path tap position
2560 if #tapPositions == 1 or goToPoint then
2561 if camera then
2562 local unitRay = camera:ScreenPointToRay(tapPositions[1].x, tapPositions[1].y)
2563 local ray = Ray.new(unitRay.Origin, unitRay.Direction*400)
2564
2565 -- inivisicam stuff
2566 local initIgnore = getIgnoreList()
2567 local invisicamParts = InvisicamModule:GetObscuredParts()
2568 local ignoreTab = {}
2569
2570 -- add to the ignore list
2571 for i, v in pairs(invisicamParts) do
2572 ignoreTab[#ignoreTab+1] = i
2573 end
2574 for i = 1, #initIgnore do
2575 ignoreTab[#ignoreTab+1] = initIgnore[i]
2576 end
2577 --
2578 local myHumanoid = findPlayerHumanoid(Player)
2579 local hitPart, hitPt, hitNormal, hitMat = Utility.Raycast(ray, true, ignoreTab)
2580
2581 local hitChar, hitHumanoid = Utility.FindChacterAncestor(hitPart)
2582 local torso = GetTorso()
2583 local startPos = torso.CFrame.p
2584 if goToPoint then
2585 hitPt = goToPoint
2586 hitChar = nil
2587 end
2588 if hitChar and hitHumanoid and hitHumanoid.Torso and (hitHumanoid.Torso.CFrame.p - torso.CFrame.p).magnitude < 7 then
2589 CleanupPath()
2590
2591 if myHumanoid then
2592 myHumanoid:MoveTo(hitPt)
2593 end
2594 -- Do shoot
2595 local currentWeapon = GetEquippedTool(character)
2596 if currentWeapon then
2597 currentWeapon:Activate()
2598 LastFired = tick()
2599 end
2600 elseif hitPt and character and not CurrentSeatPart then
2601 local thisPather = Pather(character, hitPt, hitNormal)
2602 if thisPather:IsValidPath() then
2603 FailCount = 0
2604
2605 thisPather:Start()
2606 if BindableEvent_OnFailStateChanged then
2607 BindableEvent_OnFailStateChanged:Fire(false)
2608 end
2609 CleanupPath()
2610
2611 local destinationPopup = createNewPopup("DestinationPopup")
2612 destinationPopup:Place(hitPt, Vector3_new(0,hitPt.y,0))
2613 local failurePopup = createNewPopup("FailurePopup")
2614 local currentTween = destinationPopup:TweenIn()
2615
2616
2617 ExistingPather = thisPather
2618 ExistingIndicator = destinationPopup
2619
2620 PathCompleteListener = thisPather.Finished:connect(function()
2621 if destinationPopup then
2622 if ExistingIndicator == destinationPopup then
2623 ExistingIndicator = nil
2624 end
2625 local tween = destinationPopup:TweenOut()
2626 local tweenCompleteEvent = nil
2627 tweenCompleteEvent = tween.Completed:connect(function()
2628 tweenCompleteEvent:disconnect()
2629 destinationPopup.Model:Destroy()
2630 destinationPopup = nil
2631 end)
2632 end
2633 if hitChar then
2634 local humanoid = findPlayerHumanoid(Player)
2635 local currentWeapon = GetEquippedTool(character)
2636 if currentWeapon then
2637 currentWeapon:Activate()
2638 LastFired = tick()
2639 end
2640 if humanoid then
2641
2642 humanoid:MoveTo(hitPt)
2643 end
2644 end
2645 end)
2646 PathFailedListener = thisPather.PathFailed:connect(function()
2647 if failurePopup then
2648 failurePopup:Place(hitPt, Vector3_new(0,hitPt.y,0))
2649 local failTweenIn = failurePopup:TweenIn()
2650 failTweenIn.Completed:wait()
2651 local failTweenOut = failurePopup:TweenOut()
2652 failTweenOut.Completed:wait()
2653 failurePopup.Model:Destroy()
2654 failurePopup = nil
2655 end
2656 end)
2657 else
2658 if hitPt then
2659 -- Feedback here for when we don't have a good path
2660 local foundDirectPath = false
2661 if (hitPt-startPos).Magnitude < 25 and (startPos.y-hitPt.y > -3) then
2662 -- move directly here
2663 if myHumanoid then
2664 if myHumanoid.Sit then
2665 myHumanoid.Jump = true
2666 end
2667 local currentPosition
2668 myHumanoid:MoveTo(hitPt)
2669 foundDirectPath = true
2670 end
2671 end
2672
2673 spawn(function()
2674 local directPopup = createNewPopup(foundDirectPath and "DirectWalkPopup" or "FailurePopup")
2675 directPopup:Place(hitPt, Vector3_new(0,hitPt.y,0))
2676 local directTweenIn = directPopup:TweenIn()
2677 directTweenIn.Completed:wait()
2678 local directTweenOut = directPopup:TweenOut()
2679 directTweenOut.Completed:wait()
2680 directPopup.Model:Destroy()
2681 directPopup = nil
2682 end)
2683 end
2684 end
2685 elseif hitPt and character and CurrentSeatPart then
2686 local destinationPopup = createNewPopup("DestinationPopup")
2687 ExistingIndicator = destinationPopup
2688 destinationPopup:Place(hitPt, Vector3_new(0,hitPt.y,0))
2689 destinationPopup:TweenIn()
2690
2691 DrivingTo = hitPt
2692 local ConnectedParts = CurrentSeatPart:GetConnectedParts(true)
2693
2694 while wait() do
2695 if CurrentSeatPart and ExistingIndicator == destinationPopup then
2696 local ExtentsSize = getExtentsSize(ConnectedParts)
2697 if inExtents(ExtentsSize, hitPt) then
2698 local popup = destinationPopup
2699 spawn(function()
2700 local tweenOut = popup:TweenOut()
2701 tweenOut.Completed:wait()
2702 popup.Model:Destroy()
2703 end)
2704 destinationPopup = nil
2705 DrivingTo = nil
2706 break
2707 end
2708 else
2709 if CurrentSeatPart == nil and destinationPopup == ExistingIndicator then
2710 DrivingTo = nil
2711 OnTap(tapPositions, hitPt)
2712 end
2713 local popup = destinationPopup
2714 spawn(function()
2715 print("gone")
2716 local tweenOut = popup:TweenOut()
2717 tweenOut.Completed:wait()
2718 popup.Model:Destroy()
2719 end)
2720 destinationPopup = nil
2721 break
2722 end
2723 end
2724 end
2725 end
2726 elseif #tapPositions >= 2 then
2727 if camera then
2728 -- Do shoot
2729 local avgPoint = Utility.AveragePoints(tapPositions)
2730 local unitRay = camera:ScreenPointToRay(avgPoint.x, avgPoint.y)
2731 local currentWeapon = GetEquippedTool(character)
2732 if currentWeapon then
2733 currentWeapon:Activate()
2734 LastFired = tick()
2735 end
2736 end
2737 end
2738end
2739
2740
2741local function CreateClickToMoveModule()
2742 local this = {}
2743
2744 local LastStateChange = 0
2745 local LastState = Enum.HumanoidStateType.Running
2746 local FingerTouches = {}
2747 local NumUnsunkTouches = 0
2748 -- PC simulation
2749 local mouse1Down = tick()
2750 local mouse1DownPos = Vector2_new()
2751 local mouse2Down = tick()
2752 local mouse2DownPos = Vector2_new()
2753 local mouse2Up = tick()
2754
2755 local movementKeys = {
2756 [Enum.KeyCode.W] = true;
2757 [Enum.KeyCode.A] = true;
2758 [Enum.KeyCode.S] = true;
2759 [Enum.KeyCode.D] = true;
2760 [Enum.KeyCode.Up] = true;
2761 [Enum.KeyCode.Down] = true;
2762 }
2763
2764 local TapConn = nil
2765 local InputBeganConn = nil
2766 local InputChangedConn = nil
2767 local InputEndedConn = nil
2768 local HumanoidDiedConn = nil
2769 local CharacterChildAddedConn = nil
2770 local OnCharacterAddedConn = nil
2771 local CharacterChildRemovedConn = nil
2772 local RenderSteppedConn = nil
2773 local HumanoidSeatedConn = nil
2774
2775 local function disconnectEvent(event)
2776 if event then
2777 event:disconnect()
2778 end
2779 end
2780
2781 local function DisconnectEvents()
2782 disconnectEvent(TapConn)
2783 disconnectEvent(InputBeganConn)
2784 disconnectEvent(InputChangedConn)
2785 disconnectEvent(InputEndedConn)
2786 disconnectEvent(HumanoidDiedConn)
2787 disconnectEvent(CharacterChildAddedConn)
2788 disconnectEvent(OnCharacterAddedConn)
2789 disconnectEvent(RenderSteppedConn)
2790 disconnectEvent(CharacterChildRemovedConn)
2791 pcall(function() RunService:UnbindFromRenderStep("ClickToMoveRenderUpdate") end)
2792 disconnectEvent(HumanoidSeatedConn)
2793 end
2794
2795
2796
2797 local function IsFinite(num)
2798 return num == num and num ~= 1/0 and num ~= -1/0
2799 end
2800
2801 local function findAngleBetweenXZVectors(vec2, vec1)
2802 return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
2803 end
2804
2805 -- Setup the camera
2806 CameraModule = OrbitalCamModule()
2807
2808 local function OnTouchBegan(input, processed)
2809 if FingerTouches[input] == nil and not processed then
2810 NumUnsunkTouches = NumUnsunkTouches + 1
2811 end
2812 FingerTouches[input] = processed
2813 end
2814
2815 local function OnTouchChanged(input, processed)
2816 if FingerTouches[input] == nil then
2817 FingerTouches[input] = processed
2818 if not processed then
2819 NumUnsunkTouches = NumUnsunkTouches + 1
2820 end
2821 end
2822 end
2823
2824 local function OnTouchEnded(input, processed)
2825 if FingerTouches[input] ~= nil and FingerTouches[input] == false then
2826 NumUnsunkTouches = NumUnsunkTouches - 1
2827 end
2828 FingerTouches[input] = nil
2829 end
2830
2831
2832 local function OnCharacterAdded(character)
2833 DisconnectEvents()
2834
2835 InputBeganConn = UIS.InputBegan:connect(function(input, processed)
2836 if input.UserInputType == Enum.UserInputType.Touch then
2837 OnTouchBegan(input, processed)
2838
2839 -- Give back controls when they tap both sticks
2840 local wasInBottomLeft = IsInBottomLeft(input.Position)
2841 local wasInBottomRight = IsInBottomRight(input.Position)
2842 if wasInBottomRight or wasInBottomLeft then
2843 for otherInput, _ in pairs(FingerTouches) do
2844 if otherInput ~= input then
2845 local otherInputInLeft = IsInBottomLeft(otherInput.Position)
2846 local otherInputInRight = IsInBottomRight(otherInput.Position)
2847 if otherInput.UserInputState ~= Enum.UserInputState.End and ((wasInBottomLeft and otherInputInRight) or (wasInBottomRight and otherInputInLeft)) then
2848 if BindableEvent_OnFailStateChanged then
2849 BindableEvent_OnFailStateChanged:Fire(true)
2850 end
2851 return
2852 end
2853 end
2854 end
2855 end
2856 end
2857
2858 -- Cancel path when you use the keyboard controls.
2859 if processed == false and input.UserInputType == Enum.UserInputType.Keyboard and movementKeys[input.KeyCode] then
2860 CleanupPath()
2861 end
2862 if input.UserInputType == Enum.UserInputType.MouseButton1 then
2863 mouse1Down = tick()
2864 mouse1DownPos = input.Position
2865 end
2866 if input.UserInputType == Enum.UserInputType.MouseButton2 then
2867 mouse2Down = tick()
2868 mouse2DownPos = input.Position
2869 end
2870 end)
2871
2872 InputChangedConn = UIS.InputChanged:connect(function(input, processed)
2873 if input.UserInputType == Enum.UserInputType.Touch then
2874 OnTouchChanged(input, processed)
2875 end
2876 end)
2877
2878 InputEndedConn = UIS.InputEnded:connect(function(input, processed)
2879 if input.UserInputType == Enum.UserInputType.Touch then
2880 OnTouchEnded(input, processed)
2881 end
2882
2883 if input.UserInputType == Enum.UserInputType.MouseButton2 then
2884 mouse2Up = tick()
2885 local currPos = input.Position
2886 if mouse2Up - mouse2Down < 0.25 and (currPos - mouse2DownPos).magnitude < 5 then
2887 local positions = {currPos}
2888 OnTap(positions)
2889 end
2890 end
2891 end)
2892
2893 TapConn = UIS.TouchTap:connect(function(touchPositions, processed)
2894 if not processed then
2895 OnTap(touchPositions)
2896 end
2897 end)
2898
2899 local function computeThrottle(dist)
2900 if dist > .2 then
2901 return 0.5+(dist^2)/2
2902 else
2903 return 0
2904 end
2905 end
2906
2907 local function getThrottleAndSteer(object, point)
2908 local throttle, steer = 0, 0
2909 local oCF = object.CFrame
2910 local p1 = oCF.p
2911
2912 local relativePosition = oCF:pointToObjectSpace(point)
2913 local relativeZDirection = -relativePosition.z
2914 local relativeDistance = relativePosition.magnitude
2915
2916 -- throttle quadratically increases from 0-1 as distance from the selected point goes from 0-50, after 50, throttle is 1.
2917 -- this allows shorter distance travel to have more fine-tuned control.
2918 throttle = computeThrottle(math_min(1,relativeDistance/50))*math.sign(relativeZDirection)
2919
2920 local steerAngle = -math_atan2(-relativePosition.x, -relativePosition.z)
2921 steer = steerAngle/(math_pi/4)
2922 return throttle, steer
2923 end
2924
2925 local function Update()
2926 if CameraModule then
2927 CameraModule.UserPanningTheCamera = true
2928 if CameraModule.UserPanningTheCamera then
2929 CameraModule.UpdateTweenFunction = nil
2930 else
2931 if CameraModule.UpdateTweenFunction then
2932 local done = CameraModule.UpdateTweenFunction()
2933 if done then
2934 CameraModule.UpdateTweenFunction = nil
2935 end
2936 end
2937 end
2938 CameraModule:Update()
2939 end
2940
2941 if CurrentSeatPart then
2942 if DrivingTo then
2943 local throttle, steer = getThrottleAndSteer(CurrentSeatPart, DrivingTo)
2944 CurrentSeatPart.ThrottleFloat = throttle
2945 CurrentSeatPart.SteerFloat = steer
2946 else
2947 CurrentSeatPart.ThrottleFloat = 0
2948 CurrentSeatPart.SteerFloat = 0
2949 end
2950 end
2951 end
2952
2953 RunService:BindToRenderStep("ClickToMoveRenderUpdate",Enum.RenderPriority.Camera.Value - 1,Update)
2954
2955 local function onSeated(child, active, currentSeatPart)
2956 if active then
2957 if TouchJump and UIS.TouchEnabled then
2958 TouchJump:Enable()
2959 end
2960 if currentSeatPart and currentSeatPart.ClassName == "VehicleSeat" then
2961 CurrentSeatPart = currentSeatPart
2962 end
2963 else
2964 CurrentSeatPart = nil
2965 if TouchJump and UIS.TouchEnabled then
2966 TouchJump:Disable()
2967 end
2968 end
2969 end
2970
2971 local function OnCharacterChildAdded(child)
2972 if UIS.TouchEnabled then
2973 if child:IsA('Tool') then
2974 child.ManualActivationOnly = true
2975 end
2976 end
2977 if child:IsA('Humanoid') then
2978 disconnectEvent(HumanoidDiedConn)
2979 HumanoidDiedConn = child.Died:connect(function()
2980 DebrisService:AddItem(ExistingIndicator.Model, 1)
2981 end)
2982 HumanoidSeatedConn = child.Seated:connect(function(active, seat) onSeated(child, active, seat) end)
2983 if child.SeatPart then
2984 onSeated(child, true, child.SeatPart)
2985 end
2986 end
2987 end
2988
2989 CharacterChildAddedConn = character.ChildAdded:connect(function(child)
2990 OnCharacterChildAdded(child)
2991 end)
2992 CharacterChildRemovedConn = character.ChildRemoved:connect(function(child)
2993 if UIS.TouchEnabled then
2994 if child:IsA('Tool') then
2995 child.ManualActivationOnly = false
2996 end
2997 end
2998 end)
2999 for _, child in pairs(character:GetChildren()) do
3000 OnCharacterChildAdded(child)
3001 end
3002 end
3003
3004 local Running = false
3005
3006 function this:Stop()
3007 if Running then
3008 DisconnectEvents()
3009 CleanupPath()
3010 if CameraModule then
3011 CameraModule.UpdateTweenFunction = nil
3012 CameraModule:SetEnabled(false)
3013 end
3014 -- Restore tool activation on shutdown
3015 if UIS.TouchEnabled then
3016 local character = Player.Character
3017 if character then
3018 for _, child in pairs(character:GetChildren()) do
3019 if child:IsA('Tool') then
3020 child.ManualActivationOnly = false
3021 end
3022 end
3023 end
3024 end
3025 DrivingTo = nil
3026 Running = false
3027 end
3028 end
3029
3030 function this:Start()
3031 if not Running then
3032 if Player.Character then -- retro-listen
3033 OnCharacterAdded(Player.Character)
3034 end
3035 OnCharacterAddedConn = Player.CharacterAdded:connect(OnCharacterAdded)
3036 if CameraModule then
3037 CameraModule:SetEnabled(true)
3038 end
3039 Running = true
3040 end
3041 end
3042
3043 return this
3044end
3045
3046return CreateClickToMoveModule
3047
3048--[[
3049 Invisicam
3050
3051 Modified 5.16.2017 by AllYourBlox to consider combined transparency when looking through multiple parts, and to add
3052 a mode that takes advantage of the reduced cost of ray casts from re-implementing GetPartsObscuringTarget
3053 on the C++ side to be O(N) for N parts hit, rather than O(N^2), and to have optimizations specifically
3054 improving performance of closely bundled rays. Fading is also removed, since it is a frame rate killer with high-poly models,
3055 and on mobile.
3056
3057 Based on Invisicam Version 2.5 by OnlyTwentyCharacters
3058--]]
3059
3060local Invisicam = {}
3061---------------
3062-- Constants --
3063---------------
3064local USE_STACKING_TRANSPARENCY = true -- Multiple items between the subject and camera get transparency values that add up to TARGET_TRANSPARENCY
3065local TARGET_TRANSPARENCY = 0 -- Classic Invisicam's Value, also used by new invisicam for parts hit by head and torso rays
3066local TARGET_TRANSPARENCY_PERIPHERAL = 0 -- Used by new SMART_CIRCLE mode for items not hit by head and torso rays
3067
3068local MODE = {
3069 CUSTOM = 1, -- Whatever you want!
3070 LIMBS = 2, -- Track limbs
3071 MOVEMENT = 3, -- Track movement
3072 CORNERS = 4, -- Char model corners
3073 CIRCLE1 = 5, -- Circle of casts around character
3074 CIRCLE2 = 6, -- Circle of casts around character, camera relative
3075 LIMBMOVE = 7, -- LIMBS mode + MOVEMENT mode
3076 SMART_CIRCLE = 8, -- More sample points on and around character
3077 CHAR_OUTLINE = 9,
3078}
3079
3080Invisicam.MODE = MODE
3081local STARTING_MODE = MODE.SMART_CIRCLE
3082
3083local LIMB_TRACKING_SET = {
3084 -- Common to R6, R15
3085 ['Head'] = true,
3086
3087 -- R6 Only
3088 ['Left Arm'] = true,
3089 ['Right Arm'] = true,
3090 ['Left Leg'] = true,
3091 ['Right Leg'] = true,
3092
3093 -- R15 Only
3094 ['LeftLowerArm'] = true,
3095 ['RightLowerArm'] = true,
3096 ['LeftUpperLeg'] = true,
3097 ['RightUpperLeg'] = true
3098}
3099
3100local CORNER_FACTORS = {
3101 Vector3.new(1,1,-1),
3102 Vector3.new(1,-1,-1),
3103 Vector3.new(-1,-1,-1),
3104 Vector3.new(-1,1,-1)
3105}
3106
3107local CIRCLE_CASTS = 10
3108local MOVE_CASTS = 3
3109local SMART_CIRCLE_CASTS = 24
3110local SMART_CIRCLE_INCREMENT = 2.0 * math.pi / SMART_CIRCLE_CASTS
3111local CHAR_OUTLINE_CASTS = 24
3112
3113---------------
3114-- Variables --
3115---------------
3116
3117local RunService = game:GetService('RunService')
3118local PlayersService = game:GetService('Players')
3119local Player = PlayersService.LocalPlayer
3120
3121local Camera = nil
3122local Character = nil
3123local HumanoidRootPart = nil
3124local TorsoPart = nil
3125local HeadPart = nil
3126
3127local Mode = nil
3128local BehaviorFunction = nil
3129
3130local childAddedConn = nil
3131local childRemovedConn = nil
3132
3133local Behaviors = {} -- Map of modes to behavior fns
3134local SavedHits = {} -- Objects currently being faded in/out
3135local TrackedLimbs = {} -- Used in limb-tracking casting modes
3136
3137---------------
3138--| Utility |--
3139---------------
3140
3141local math_min = math.min
3142local math_max = math.max
3143local math_cos = math.cos
3144local math_sin = math.sin
3145local math_pi = math.pi
3146
3147local Vector3_new = Vector3.new
3148local ZERO_VECTOR3 = Vector3_new(0,0,0)
3149
3150local function AssertTypes(param, ...)
3151 local allowedTypes = {}
3152 local typeString = ''
3153 for _, typeName in pairs({...}) do
3154 allowedTypes[typeName] = true
3155 typeString = typeString .. (typeString == '' and '' or ' or ') .. typeName
3156 end
3157 local theType = type(param)
3158 assert(allowedTypes[theType], typeString .. " type expected, got: " .. theType)
3159end
3160
3161-----------------------
3162--| Local Functions |--
3163-----------------------
3164
3165local function LimbBehavior(castPoints)
3166 for limb, _ in pairs(TrackedLimbs) do
3167 castPoints[#castPoints + 1] = limb.Position
3168 end
3169end
3170
3171local function MoveBehavior(castPoints)
3172 for i = 1, MOVE_CASTS do
3173 local position, velocity = HumanoidRootPart.Position, HumanoidRootPart.Velocity
3174 local horizontalSpeed = Vector3_new(velocity.X, 0, velocity.Z).Magnitude / 2
3175 local offsetVector = (i - 1) * HumanoidRootPart.CFrame.lookVector * horizontalSpeed
3176 castPoints[#castPoints + 1] = position + offsetVector
3177 end
3178end
3179
3180local function CornerBehavior(castPoints)
3181 local cframe = HumanoidRootPart.CFrame
3182 local centerPoint = cframe.p
3183 local rotation = cframe - centerPoint
3184 local halfSize = Character:GetExtentsSize() / 2 --NOTE: Doesn't update w/ limb animations
3185 castPoints[#castPoints + 1] = centerPoint
3186 for i = 1, #CORNER_FACTORS do
3187 castPoints[#castPoints + 1] = centerPoint + (rotation * (halfSize * CORNER_FACTORS[i]))
3188 end
3189end
3190
3191local function CircleBehavior(castPoints)
3192 local cframe = nil
3193 if Mode == MODE.CIRCLE1 then
3194 cframe = HumanoidRootPart.CFrame
3195 else
3196 local camCFrame = Camera.CoordinateFrame
3197 cframe = camCFrame - camCFrame.p + HumanoidRootPart.Position
3198 end
3199 castPoints[#castPoints + 1] = cframe.p
3200 for i = 0, CIRCLE_CASTS - 1 do
3201 local angle = (2 * math_pi / CIRCLE_CASTS) * i
3202 local offset = 3 * Vector3_new(math_cos(angle), math_sin(angle), 0)
3203 castPoints[#castPoints + 1] = cframe * offset
3204 end
3205end
3206
3207local function LimbMoveBehavior(castPoints)
3208 LimbBehavior(castPoints)
3209 MoveBehavior(castPoints)
3210end
3211
3212local function CharacterOutlineBehavior(castPoints)
3213 local torsoUp = TorsoPart.CFrame.upVector.unit
3214 local torsoRight = TorsoPart.CFrame.rightVector.unit
3215
3216 -- Torso cross of points for interior coverage
3217 castPoints[#castPoints + 1] = TorsoPart.CFrame.p
3218 castPoints[#castPoints + 1] = TorsoPart.CFrame.p + torsoUp
3219 castPoints[#castPoints + 1] = TorsoPart.CFrame.p - torsoUp
3220 castPoints[#castPoints + 1] = TorsoPart.CFrame.p + torsoRight
3221 castPoints[#castPoints + 1] = TorsoPart.CFrame.p - torsoRight
3222 if HeadPart then
3223 castPoints[#castPoints + 1] = HeadPart.CFrame.p
3224 end
3225
3226 local cframe = CFrame.new(ZERO_VECTOR3,Vector3_new(Camera.CoordinateFrame.lookVector.X,0,Camera.CoordinateFrame.lookVector.Z))
3227 local centerPoint = (TorsoPart and TorsoPart.Position or HumanoidRootPart.Position)
3228
3229 local partsWhitelist = {TorsoPart}
3230 if HeadPart then
3231 partsWhitelist[#partsWhitelist + 1] = HeadPart
3232 end
3233
3234 for i = 1, CHAR_OUTLINE_CASTS do
3235 local angle = (2 * math_pi * i / CHAR_OUTLINE_CASTS)
3236 local offset = cframe * (3 * Vector3_new(math_cos(angle), math_sin(angle), 0))
3237
3238 offset = Vector3_new(offset.X, math_max(offset.Y, -2.25), offset.Z)
3239
3240 local ray = Ray.new(centerPoint + offset, -3 * offset)
3241 local hit, hitPoint = game.Workspace:FindPartOnRayWithWhitelist(ray, partsWhitelist, false, false)
3242
3243 if hit then
3244 -- Use hit point as the cast point, but nudge it slightly inside the character so that bumping up against
3245 -- walls is less likely to cause a transparency glitch
3246 castPoints[#castPoints + 1] = hitPoint + 0.2 * (centerPoint - hitPoint).unit
3247 end
3248 end
3249end
3250
3251-- Helper function for Determinant of 3x3
3252local function Det3x3(a,b,c,d,e,f,g,h,i)
3253 return (a*(e*i-f*h)-b*(d*i-f*g)+c*(d*h-e*g))
3254end
3255
3256-- Smart Circle mode needs the intersection of 2 rays that are known to be in the same plane
3257-- because they are generated from cross products with a common vector. This function is computing
3258-- that intersection, but it's actually the general solution for the point halfway between where
3259-- two skew lines come nearest to each other, which is more forgiving.
3260local function RayIntersection(p0, v0, p1, v1)
3261 local v2 = v0:Cross(v1)
3262 local d1 = p1.x - p0.x
3263 local d2 = p1.y - p0.y
3264 local d3 = p1.z - p0.z
3265 local denom = Det3x3(v0.x,-v1.x,v2.x,v0.y,-v1.y,v2.y,v0.z,-v1.z,v2.z)
3266
3267 if (denom == 0) then
3268 return ZERO_VECTOR3 -- No solution (rays are parallel)
3269 end
3270
3271 local t0 = Det3x3(d1,-v1.x,v2.x,d2,-v1.y,v2.y,d3,-v1.z,v2.z) / denom
3272 local t1 = Det3x3(v0.x,d1,v2.x,v0.y,d2,v2.y,v0.z,d3,v2.z) / denom
3273 local s0 = p0 + t0 * v0
3274 local s1 = p1 + t1 * v1
3275 local s = s0 + 0.5 * ( s1 - s0 )
3276
3277 -- 0.25 studs is a threshold for deciding if the rays are
3278 -- close enough to be considered intersecting, found through testing
3279 if (s1-s0).Magnitude < 0.25 then
3280 return s
3281 else
3282 return ZERO_VECTOR3
3283 end
3284end
3285
3286local function SmartCircleBehavior(castPoints)
3287 local torsoUp = TorsoPart.CFrame.upVector.unit
3288 local torsoRight = TorsoPart.CFrame.rightVector.unit
3289
3290 -- SMART_CIRCLE mode includes rays to head and 5 to the torso.
3291 -- Hands, arms, legs and feet are not included since they
3292 -- are not canCollide and can therefore go inside of parts
3293 castPoints[#castPoints + 1] = TorsoPart.CFrame.p
3294 castPoints[#castPoints + 1] = TorsoPart.CFrame.p + torsoUp
3295 castPoints[#castPoints + 1] = TorsoPart.CFrame.p - torsoUp
3296 castPoints[#castPoints + 1] = TorsoPart.CFrame.p + torsoRight
3297 castPoints[#castPoints + 1] = TorsoPart.CFrame.p - torsoRight
3298 if HeadPart then
3299 castPoints[#castPoints + 1] = HeadPart.CFrame.p
3300 end
3301
3302 local cameraOrientation = Camera.CFrame - Camera.CFrame.p
3303 local torsoPoint = Vector3_new(0,0.5,0) + (TorsoPart and TorsoPart.Position or HumanoidRootPart.Position)
3304 local radius = 2.5
3305
3306 -- This loop first calculates points in a circle of radius 2.5 around the torso of the character, in the
3307 -- plane orthogonal to the camera's lookVector. Each point is then raycast to, to determine if it is within
3308 -- the free space surrounding the player (not inside anything). Two iterations are done to adjust points that
3309 -- are inside parts, to try to move them to valid locations that are still on their camera ray, so that the
3310 -- circle remains circular from the camera's perspective, but does not cast rays into walls or parts that are
3311 -- behind, below or beside the character and not really obstructing view of the character. This minimizes
3312 -- the undesirable situation where the character walks up to an exterior wall and it is made invisible even
3313 -- though it is behind the character.
3314 for i = 1, SMART_CIRCLE_CASTS do
3315 local angle = SMART_CIRCLE_INCREMENT * i - 0.5 * math.pi
3316 local offset = radius * Vector3_new(math_cos(angle), math_sin(angle), 0)
3317 local circlePoint = torsoPoint + cameraOrientation * offset
3318
3319 -- Vector from camera to point on the circle being tested
3320 local vp = circlePoint - Camera.CFrame.p
3321
3322 local ray = Ray.new(torsoPoint, circlePoint - torsoPoint)
3323 local hit, hp, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {Character}, false, false )
3324 local castPoint = circlePoint
3325
3326 if hit then
3327 local hprime = hp + 0.1 * hitNormal.unit -- Slightly offset hit point from the hit surface
3328 local v0 = hprime - torsoPoint -- Vector from torso to offset hit point
3329 local d0 = v0.magnitude
3330
3331 local perp = (v0:Cross(vp)).unit
3332
3333 -- Vector from the offset hit point, along the hit surface
3334 local v1 = (perp:Cross(hitNormal)).unit
3335
3336 -- Vector from camera to offset hit
3337 local vprime = (hprime - Camera.CFrame.p).unit
3338
3339 -- This dot product checks to see if the vector along the hit surface would hit the correct
3340 -- side of the invisicam cone, or if it would cross the camera look vector and hit the wrong side
3341 if ( v0.unit:Dot(-v1) < v0.unit:Dot(vprime)) then
3342 castPoint = RayIntersection(hprime, v1, circlePoint, vp)
3343
3344 if castPoint.Magnitude > 0 then
3345 local ray = Ray.new(hprime, castPoint - hprime)
3346 local hit, hitPoint, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {Character}, false, false )
3347
3348 if hit then
3349 local hprime2 = hitPoint + 0.1 * hitNormal.unit
3350 castPoint = hprime2
3351 end
3352 else
3353 castPoint = hprime
3354 end
3355 else
3356 castPoint = hprime
3357 end
3358
3359 local ray = Ray.new(torsoPoint, (castPoint - torsoPoint))
3360 local hit, hitPoint, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {Character}, false, false )
3361
3362 if hit then
3363 local castPoint2 = hitPoint - 0.1 * (castPoint - torsoPoint).unit
3364 castPoint = castPoint2
3365 end
3366 end
3367
3368 castPoints[#castPoints + 1] = castPoint
3369 end
3370end
3371
3372local function CheckTorsoReference()
3373 if Character then
3374 TorsoPart = Character:FindFirstChild("Torso")
3375 if not TorsoPart then
3376 TorsoPart = Character:FindFirstChild("UpperTorso")
3377 if not TorsoPart then
3378 TorsoPart = Character:FindFirstChild("HumanoidRootPart")
3379 end
3380 end
3381
3382 HeadPart = Character:FindFirstChild("Head")
3383 end
3384end
3385
3386local function OnCharacterAdded(character)
3387 if childAddedConn then
3388 childAddedConn:disconnect()
3389 childAddedConn = nil
3390 end
3391 if childRemovedConn then
3392 childRemovedConn:disconnect()
3393 childRemovedConn = nil
3394 end
3395
3396 Character = character
3397
3398 TrackedLimbs = {}
3399 local function childAdded(child)
3400 if child:IsA('BasePart') then
3401 if LIMB_TRACKING_SET[child.Name] then
3402 TrackedLimbs[child] = true
3403 end
3404
3405 if (child.Name == 'Torso' or child.Name == 'UpperTorso') then
3406 TorsoPart = child
3407 end
3408
3409 if (child.Name == 'Head') then
3410 HeadPart = child
3411 end
3412 end
3413 end
3414
3415 local function childRemoved(child)
3416 TrackedLimbs[child] = nil
3417
3418 -- If removed/replaced part is 'Torso' or 'UpperTorso' double check that we still have a TorsoPart to use
3419 CheckTorsoReference()
3420 end
3421
3422 childAddedConn = character.ChildAdded:connect(childAdded)
3423 childRemovedConn = character.ChildRemoved:connect(childRemoved)
3424
3425 for _, child in pairs(Character:GetChildren()) do
3426 childAdded(child)
3427 end
3428end
3429
3430local function OnCurrentCameraChanged()
3431 local newCamera = workspace.CurrentCamera
3432 if newCamera then
3433 Camera = newCamera
3434 end
3435end
3436
3437-----------------------
3438-- Exposed Functions --
3439-----------------------
3440
3441-- Update. Called every frame after the camera movement step
3442function Invisicam:Update()
3443 -- Make sure we still have a HumanoidRootPart
3444 if not HumanoidRootPart then
3445 local humanoid = Character:FindFirstChildOfClass("Humanoid")
3446 if humanoid and humanoid.Torso then
3447 HumanoidRootPart = humanoid.Torso
3448 else
3449 -- Not set up with Humanoid? Try and see if there's one in the Character at all:
3450 HumanoidRootPart = Character:FindFirstChild("HumanoidRootPart")
3451 if not HumanoidRootPart then
3452 -- Bail out, since we're relying on HumanoidRootPart existing
3453 return
3454 end
3455 end
3456 local ancestryChangedConn;
3457 ancestryChangedConn = HumanoidRootPart.AncestryChanged:connect(function(child, parent)
3458 if child == HumanoidRootPart and not parent then
3459 HumanoidRootPart = nil
3460 if ancestryChangedConn and ancestryChangedConn.Connected then
3461 ancestryChangedConn:Disconnect()
3462 ancestryChangedConn = nil
3463 end
3464 end
3465 end)
3466 end
3467
3468 if not TorsoPart then
3469 CheckTorsoReference()
3470 if not TorsoPart then
3471 -- Bail out, since we're relying on Torso existing, should never happen since we fall back to using HumanoidRootPart as torso
3472 return
3473 end
3474 end
3475
3476 -- Make a list of world points to raycast to
3477 local castPoints = {}
3478 BehaviorFunction(castPoints)
3479
3480 -- Cast to get a list of objects between the camera and the cast points
3481 local currentHits = {}
3482 local ignoreList = {Character}
3483 local function add(hit)
3484 currentHits[hit] = true
3485 if not SavedHits[hit] then
3486 SavedHits[hit] = hit.LocalTransparencyModifier
3487 end
3488 end
3489
3490 local hitParts
3491 local hitPartCount = 0
3492
3493 -- Hash table to treat head-ray-hit parts differently than the rest of the hit parts hit by other rays
3494 -- head/torso ray hit parts will be more transparent than peripheral parts when USE_STACKING_TRANSPARENCY is enabled
3495 local headTorsoRayHitParts = {}
3496 local partIsTouchingCamera = {}
3497
3498 local perPartTransparencyHeadTorsoHits = TARGET_TRANSPARENCY
3499 local perPartTransparencyOtherHits = TARGET_TRANSPARENCY
3500
3501 if USE_STACKING_TRANSPARENCY then
3502
3503 -- This first call uses head and torso rays to find out how many parts are stacked up
3504 -- for the purpose of calculating required per-part transparency
3505 local headPoint = HeadPart and HeadPart.CFrame.p or castPoints[1]
3506 local torsoPoint = TorsoPart and TorsoPart.CFrame.p or castPoints[2]
3507 hitParts = Camera:GetPartsObscuringTarget({headPoint, torsoPoint}, ignoreList)
3508
3509 -- Count how many things the sample rays passed through, including decals. This should only
3510 -- count decals facing the camera, but GetPartsObscuringTarget does not return surface normals,
3511 -- so my compromise for now is to just let any decal increase the part count by 1. Only one
3512 -- decal per part will be considered.
3513 for i = 1, #hitParts do
3514 local hitPart = hitParts[i]
3515 hitPartCount = hitPartCount + 1 -- count the part itself
3516 headTorsoRayHitParts[hitPart] = true
3517 for _, child in pairs(hitPart:GetChildren()) do
3518 if child:IsA('Decal') or child:IsA('Texture') then
3519 hitPartCount = hitPartCount + 1 -- count first decal hit, then break
3520 break
3521 end
3522 end
3523 end
3524
3525 if (hitPartCount > 0) then
3526 perPartTransparencyHeadTorsoHits = math.pow( ((0 * TARGET_TRANSPARENCY) + (0 * TARGET_TRANSPARENCY / hitPartCount)), 1 / hitPartCount )
3527 perPartTransparencyOtherHits = math.pow( ((0 * TARGET_TRANSPARENCY_PERIPHERAL) + (0 * TARGET_TRANSPARENCY_PERIPHERAL / hitPartCount)), 1 / hitPartCount )
3528 end
3529 end
3530
3531 -- Now get all the parts hit by all the rays
3532 hitParts = Camera:GetPartsObscuringTarget(castPoints, ignoreList)
3533
3534 -- This accounts for parts the camera may actually be inside. There is a case where the camera can be inside
3535 -- the bounding box of a concave mesh part, and a solid part of the mesh can obscure the camera since
3536 -- rays coming from the camera would not be considered as hitting the part. This code adds all parts
3537 -- that touch a 2x2x2 box around the camera position.
3538 local distance = (TorsoPart.CFrame.p - Camera.CoordinateFrame.p).magnitude
3539 local region3 = Region3.new(Camera.CFrame.p + Vector3_new(-1,-1,-1), Camera.CFrame.p + Vector3_new(1,1,1))
3540 local partsTouchingCamera
3541 if distance < 12 then
3542 partsTouchingCamera = game.Workspace:FindPartsInRegion3(region3,Character,math.huge)
3543 else
3544 partsTouchingCamera = game.Workspace:FindPartsInRegion3(region3,nil,10)
3545 end
3546
3547 if partsTouchingCamera then
3548 for _,part in pairs(partsTouchingCamera) do
3549 hitParts[#hitParts+1] = part
3550 partIsTouchingCamera[part] = true
3551 end
3552 end
3553
3554 local partTargetTransparency = {}
3555
3556 -- Include decals and textures
3557 for i = 1, #hitParts do
3558 local hitPart = hitParts[i]
3559
3560 partTargetTransparency[hitPart] = partIsTouchingCamera[hitPart] and 1.0 or (headTorsoRayHitParts[hitPart] and perPartTransparencyHeadTorsoHits or perPartTransparencyOtherHits)
3561
3562 -- If the part is not already as transparent or more transparent than what invisicam requires, add it to the list of
3563 -- parts to be modified by invisicam
3564 if hitPart.Transparency < partTargetTransparency[hitPart] then
3565 add(hitPart)
3566 end
3567
3568 -- Check all decals and textures on the part
3569 for _, child in pairs(hitPart:GetChildren()) do
3570 if child:IsA('Decal') or child:IsA('Texture') then
3571 if (child.Transparency < partTargetTransparency[hitPart]) then
3572 partTargetTransparency[child] = partTargetTransparency[hitPart]
3573 add(child)
3574 end
3575 end
3576 end
3577 end
3578
3579 -- Invisibilize objects that are in the way, restore those that aren't anymore
3580 for hitPart, originalLTM in pairs(SavedHits) do
3581 if currentHits[hitPart] then
3582 -- LocalTransparencyModifier gets whatever value is required to print the part's total transparency to equal perPartTransparency
3583 hitPart.LocalTransparencyModifier = (hitPart.Transparency < 1) and ((partTargetTransparency[hitPart] - hitPart.Transparency) / (0 - hitPart.Transparency)) or 0
3584 else -- Restore original pre-invisicam value of LTM
3585 hitPart.LocalTransparencyModifier = originalLTM
3586 SavedHits[hitPart] = nil
3587 end
3588 end
3589end
3590
3591function Invisicam:SetMode(newMode)
3592 AssertTypes(newMode, 'number')
3593 for modeName, modeNum in pairs(MODE) do
3594 if modeNum == newMode then
3595 Mode = newMode
3596 BehaviorFunction = Behaviors[Mode]
3597 return
3598 end
3599 end
3600 error("Invalid mode number")
3601end
3602
3603function Invisicam:SetCustomBehavior(func)
3604 AssertTypes(func, 'function')
3605 Behaviors[MODE.CUSTOM] = func
3606 if Mode == MODE.CUSTOM then
3607 BehaviorFunction = func
3608 end
3609end
3610
3611function Invisicam:GetObscuredParts()
3612 return SavedHits
3613end
3614
3615-- Want to turn off Invisicam? Be sure to call this after.
3616function Invisicam:Cleanup()
3617 for hit, originalFade in pairs(SavedHits) do
3618 hit.LocalTransparencyModifier = originalFade
3619 end
3620end
3621
3622---------------------
3623--| Running Logic |--
3624---------------------
3625
3626-- Connect to the current and all future cameras
3627workspace:GetPropertyChangedSignal("CurrentCamera"):connect(OnCurrentCameraChanged)
3628OnCurrentCameraChanged()
3629
3630Player.CharacterAdded:connect(OnCharacterAdded)
3631if Player.Character then
3632 OnCharacterAdded(Player.Character)
3633end
3634
3635Behaviors[MODE.CUSTOM] = function() end -- (Does nothing until SetCustomBehavior)
3636Behaviors[MODE.LIMBS] = LimbBehavior
3637Behaviors[MODE.MOVEMENT] = MoveBehavior
3638Behaviors[MODE.CORNERS] = CornerBehavior
3639Behaviors[MODE.CIRCLE1] = CircleBehavior
3640Behaviors[MODE.CIRCLE2] = CircleBehavior
3641Behaviors[MODE.LIMBMOVE] = LimbMoveBehavior
3642Behaviors[MODE.SMART_CIRCLE] = SmartCircleBehavior
3643Behaviors[MODE.CHAR_OUTLINE] = CharacterOutlineBehavior
3644
3645Invisicam:SetMode(STARTING_MODE)
3646
3647return Invisicam
3648
3649-- PopperCam Version 16
3650-- OnlyTwentyCharacters
3651
3652local PopperCam = {} -- Guarantees your players won't see outside the bounds of your map!
3653
3654-----------------
3655--| Constants |--
3656-----------------
3657
3658local POP_RESTORE_RATE = 0.3
3659local MIN_CAMERA_ZOOM = 0.5
3660
3661local VALID_SUBJECTS = {
3662 'Humanoid',
3663 'VehicleSeat',
3664 'SkateboardPlatform',
3665}
3666
3667local portraitPopperFixFlagExists, portraitPopperFixFlagEnabled = pcall(function()
3668 return UserSettings():IsUserFeatureEnabled("UserPortraitPopperFix")
3669end)
3670local FFlagUserPortraitPopperFix = portraitPopperFixFlagExists and portraitPopperFixFlagEnabled
3671
3672-----------------
3673--| Variables |--
3674-----------------
3675
3676local PlayersService = game:GetService('Players')
3677
3678local Camera = nil
3679local CameraSubjectChangeConn = nil
3680
3681local SubjectPart = nil
3682
3683local PlayerCharacters = {} -- For ignoring in raycasts
3684local VehicleParts = {} -- Also just for ignoring
3685
3686local LastPopAmount = 0
3687local LastZoomLevel = 0
3688local PopperEnabled = false
3689
3690local CFrame_new = CFrame.new
3691
3692-----------------------
3693--| Local Functions |--
3694-----------------------
3695
3696local math_abs = math.abs
3697
3698local function OnCameraSubjectChanged()
3699 VehicleParts = {}
3700
3701 local newSubject = Camera.CameraSubject
3702 if newSubject then
3703 -- Determine if we should be popping at all
3704 PopperEnabled = false
3705 for _, subjectType in pairs(VALID_SUBJECTS) do
3706 if newSubject:IsA(subjectType) then
3707 PopperEnabled = true
3708 break
3709 end
3710 end
3711
3712 -- Get all parts of the vehicle the player is controlling
3713 if newSubject:IsA('VehicleSeat') then
3714 VehicleParts = newSubject:GetConnectedParts(true)
3715 end
3716
3717 if FFlagUserPortraitPopperFix then
3718 if newSubject:IsA("BasePart") then
3719 SubjectPart = newSubject
3720 elseif newSubject:IsA("Model") then
3721 SubjectPart = newSubject.PrimaryPart
3722 elseif newSubject:IsA("Humanoid") then
3723 SubjectPart = newSubject.Torso
3724 end
3725 end
3726 end
3727end
3728
3729local function OnCharacterAdded(player, character)
3730 PlayerCharacters[player] = character
3731end
3732
3733local function OnPlayersChildAdded(child)
3734 if child:IsA('Player') then
3735 child.CharacterAdded:connect(function(character)
3736 OnCharacterAdded(child, character)
3737 end)
3738 if child.Character then
3739 OnCharacterAdded(child, child.Character)
3740 end
3741 end
3742end
3743
3744local function OnPlayersChildRemoved(child)
3745 if child:IsA('Player') then
3746 PlayerCharacters[child] = nil
3747 end
3748end
3749
3750 local function OnWorkspaceChanged(property)
3751 if property == 'CurrentCamera' then
3752 local newCamera = workspace.CurrentCamera
3753 if newCamera then
3754 Camera = newCamera
3755
3756 if CameraSubjectChangeConn then
3757 CameraSubjectChangeConn:disconnect()
3758 end
3759
3760 CameraSubjectChangeConn = Camera:GetPropertyChangedSignal("CameraSubject"):connect(OnCameraSubjectChanged)
3761 OnCameraSubjectChanged()
3762 end
3763 end
3764end
3765
3766-------------------------
3767--| Exposed Functions |--
3768-------------------------
3769
3770function PopperCam:Update(EnabledCamera)
3771 if PopperEnabled then
3772 -- First, prep some intermediate vars
3773 local cameraCFrame = Camera.CFrame
3774 local focusPoint = Camera.Focus.p
3775
3776 if FFlagUserPortraitPopperFix and SubjectPart then
3777 focusPoint = SubjectPart.CFrame.p
3778 end
3779
3780 local ignoreList = {}
3781 for _, character in pairs(PlayerCharacters) do
3782 ignoreList[#ignoreList + 1] = character
3783 end
3784 for i = 1, #VehicleParts do
3785 ignoreList[#ignoreList + 1] = VehicleParts[i]
3786 end
3787
3788 -- Get largest cutoff distance
3789 local largest = Camera:GetLargestCutoffDistance(ignoreList)
3790
3791 -- Then check if the player zoomed since the last frame,
3792 -- and if so, reset our pop history so we stop tweening
3793 local zoomLevel = (cameraCFrame.p - focusPoint).Magnitude
3794 if math_abs(zoomLevel - LastZoomLevel) > 0.001 then
3795 LastPopAmount = 0
3796 end
3797
3798 -- Finally, zoom the camera in (pop) by that most-cut-off amount, or the last pop amount if that's more
3799 local popAmount = largest
3800 if LastPopAmount > popAmount then
3801 popAmount = LastPopAmount
3802 end
3803
3804 if popAmount > 0 then
3805 Camera.CFrame = cameraCFrame + (cameraCFrame.lookVector * popAmount)
3806 LastPopAmount = popAmount - POP_RESTORE_RATE -- Shrink it for the next frame
3807 if LastPopAmount < 0 then
3808 LastPopAmount = 0
3809 end
3810 end
3811
3812 LastZoomLevel = zoomLevel
3813
3814 -- Stop shift lock being able to see through walls by manipulating Camera focus inside the wall
3815 if EnabledCamera and EnabledCamera:GetShiftLock() and not EnabledCamera:IsInFirstPerson() then
3816 if EnabledCamera:GetCameraActualZoom() < 1 then
3817 local subjectPosition = EnabledCamera.lastSubjectPosition
3818 if subjectPosition then
3819 Camera.Focus = CFrame_new(subjectPosition)
3820 Camera.CFrame = CFrame_new(subjectPosition - MIN_CAMERA_ZOOM*EnabledCamera:GetCameraLook(), subjectPosition)
3821 end
3822 end
3823 end
3824 end
3825end
3826
3827--------------------
3828--| Script Logic |--
3829--------------------
3830
3831-- Connect to the current and all future cameras
3832workspace.Changed:connect(OnWorkspaceChanged)
3833OnWorkspaceChanged('CurrentCamera')
3834
3835-- Connect to all Players so we can ignore their Characters
3836PlayersService.ChildRemoved:connect(OnPlayersChildRemoved)
3837PlayersService.ChildAdded:connect(OnPlayersChildAdded)
3838for _, player in pairs(PlayersService:GetPlayers()) do
3839 OnPlayersChildAdded(player)
3840end
3841
3842return PopperCam
3843
3844
3845local PlayersService = game:GetService('Players')
3846local UserInputService = game:GetService('UserInputService')
3847local StarterGui = game:GetService('StarterGui')
3848local GuiService = game:GetService('GuiService')
3849local ContextActionService = game:GetService('ContextActionService')
3850local VRService = game:GetService("VRService")
3851
3852local LocalPlayer = PlayersService.LocalPlayer
3853local PlayerGui = nil
3854if LocalPlayer then
3855 PlayerGui = PlayersService.LocalPlayer:WaitForChild("PlayerGui")
3856end
3857
3858local PortraitMode = false
3859
3860local CameraScript = script.Parent
3861local ShiftLockController = require(CameraScript:WaitForChild('ShiftLockController'))
3862
3863local Settings = UserSettings()
3864local GameSettings = Settings.GameSettings
3865
3866local function clamp(low, high, num)
3867 return (num > high and high or num < low and low or num)
3868end
3869
3870local math_atan2 = math.atan2
3871local function findAngleBetweenXZVectors(vec2, vec1)
3872 return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
3873end
3874
3875local function IsFinite(num)
3876 return num == num and num ~= 1/0 and num ~= -1/0
3877end
3878
3879local THUMBSTICK_DEADZONE = 0.2
3880
3881local LANDSCAPE_DEFAULT_ZOOM = 12.5
3882local PORTRAIT_DEFAULT_ZOOM = 25
3883
3884local humanoidCache = {}
3885local function findPlayerHumanoid(player)
3886 local character = player and player.Character
3887 if character then
3888 local resultHumanoid = humanoidCache[player]
3889 if resultHumanoid and resultHumanoid.Parent == character then
3890 return resultHumanoid
3891 else
3892 humanoidCache[player] = nil -- Bust Old Cache
3893 local humanoid = character:FindFirstChildOfClass("Humanoid")
3894 if humanoid then
3895 humanoidCache[player] = humanoid
3896 end
3897 return humanoid
3898 end
3899 end
3900end
3901
3902local MIN_Y = math.rad(-80)
3903local MAX_Y = math.rad(80)
3904
3905local VR_ANGLE = math.rad(15)
3906
3907local VR_LOW_INTENSITY_ROTATION = Vector2.new(math.rad(15), 0)
3908local VR_HIGH_INTENSITY_ROTATION = Vector2.new(math.rad(45), 0)
3909local VR_LOW_INTENSITY_REPEAT = 0.1
3910local VR_HIGH_INTENSITY_REPEAT = 0.4
3911
3912local ZERO_VECTOR2 = Vector2.new(0, 0)
3913local ZERO_VECTOR3 = Vector3.new(0, 0, 0)
3914
3915local TOUCH_SENSITIVTY = Vector2.new(math.pi*2.25, math.pi*2)
3916local MOUSE_SENSITIVITY = Vector2.new(math.pi*4, math.pi*1.9)
3917
3918local MAX_TIME_FOR_DOUBLE_TAP = 1.5
3919local MAX_TAP_POS_DELTA = 15
3920local MAX_TAP_TIME_DELTA = 0.75
3921
3922local SEAT_OFFSET = Vector3.new(0,5,0)
3923local VR_SEAT_OFFSET = Vector3.new(0, 4, 0)
3924local HEAD_OFFSET = Vector3.new(0, 1.5, 0)
3925local R15_HEAD_OFFSET = Vector3.new(0, 2.0, 0)
3926
3927-- Reset the camera look vector when the camera is enabled for the first time
3928local SetCameraOnSpawn = true
3929
3930local hasGameLoaded = false
3931
3932local GestureArea = nil
3933
3934--todo: remove this once TouchTapInWorld is on all platforms
3935local touchWorkspaceEventEnabled = pcall(function() local test = UserInputService.TouchTapInWorld end)
3936
3937local function layoutGestureArea(portraitMode)
3938 if GestureArea then
3939 if portraitMode then
3940 GestureArea.Size = UDim2.new(1, 0, .6, 0)
3941 GestureArea.Position = UDim2.new(0, 0, 0, 0)
3942 else
3943 GestureArea.Size = UDim2.new(1, 0, .5, -18)
3944 GestureArea.Position = UDim2.new(0, 0, 0, 0)
3945 end
3946 end
3947end
3948
3949-- Setup gesture area that camera uses while DynamicThumbstick is enabled
3950local function OnCharacterAdded(character)
3951 if UserInputService.TouchEnabled then
3952 if PlayerGui then
3953 local ScreenGui = Instance.new("ScreenGui")
3954 ScreenGui.Parent = PlayerGui
3955
3956 GestureArea = Instance.new("Frame")
3957 GestureArea.BackgroundTransparency = 1.0
3958 GestureArea.Visible = true
3959 GestureArea.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
3960 layoutGestureArea(PortraitMode)
3961 GestureArea.Parent = ScreenGui
3962
3963 end
3964 for _, child in ipairs(LocalPlayer.Character:GetChildren()) do
3965 if child:IsA("Tool") then
3966 IsAToolEquipped = true
3967 end
3968 end
3969 character.ChildAdded:Connect(function(child)
3970 if child:IsA("Tool") then
3971 IsAToolEquipped = true
3972 end
3973 end)
3974 character.ChildRemoved:Connect(function(child)
3975 if child:IsA("Tool") then
3976 IsAToolEquipped = false
3977 end
3978 end)
3979 end
3980end
3981
3982if LocalPlayer then
3983 if LocalPlayer.Character ~= nil then
3984 OnCharacterAdded(LocalPlayer.Character)
3985 end
3986 LocalPlayer.CharacterAdded:connect(function(character)
3987 OnCharacterAdded(character)
3988 end)
3989end
3990
3991
3992local function positionIntersectsGuiObject(position, guiObject)
3993 if position.X < guiObject.AbsolutePosition.X + guiObject.AbsoluteSize.X
3994 and position.X > guiObject.AbsolutePosition.X
3995 and position.Y < guiObject.AbsolutePosition.Y + guiObject.AbsoluteSize.Y
3996 and position.Y > guiObject.AbsolutePosition.Y then
3997 return true
3998 end
3999 return false
4000end
4001
4002local function GetRenderCFrame(part)
4003 return part:GetRenderCFrame()
4004end
4005
4006local function CreateCamera()
4007 local this = {}
4008 local R15HeadHeight = R15_HEAD_OFFSET
4009 function this:GetActivateValue()
4010 return 0.7
4011 end
4012
4013 function this:IsPortraitMode()
4014 return PortraitMode
4015 end
4016
4017 function this:GetRotateAmountValue(vrRotationIntensity)
4018 vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
4019 if vrRotationIntensity then
4020 if vrRotationIntensity == "Low" then
4021 return VR_LOW_INTENSITY_ROTATION
4022 elseif vrRotationIntensity == "High" then
4023 return VR_HIGH_INTENSITY_ROTATION
4024 end
4025 end
4026 return ZERO_VECTOR2
4027 end
4028 function this:GetRepeatDelayValue(vrRotationIntensity)
4029 vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
4030 if vrRotationIntensity then
4031 if vrRotationIntensity == "Low" then
4032 return VR_LOW_INTENSITY_REPEAT
4033 elseif vrRotationIntensity == "High" then
4034 return VR_HIGH_INTENSITY_REPEAT
4035 end
4036 end
4037 return 0
4038 end
4039
4040 this.ShiftLock = false
4041 this.Enabled = false
4042 local isFirstPerson = false
4043 local isRightMouseDown = false
4044 local isMiddleMouseDown = false
4045 this.RotateInput = ZERO_VECTOR2
4046 this.DefaultZoom = LANDSCAPE_DEFAULT_ZOOM
4047 this.activeGamepad = nil
4048
4049 local tweens = {}
4050
4051 this.lastSubject = nil
4052 this.lastSubjectPosition = Vector3.new(0, 5, 0)
4053
4054 local lastVRRotation = 0
4055 local vrRotateKeyCooldown = {}
4056
4057 local isDynamicThumbstickEnabled = false
4058
4059 -- Check for changes in ViewportSize to decide if PortraitMode
4060 local CameraChangedConn = nil
4061 local workspaceCameraChangedConn = nil
4062 local function onWorkspaceCameraChanged()
4063 if UserInputService.TouchEnabled then
4064 if CameraChangedConn then
4065 CameraChangedConn:Disconnect()
4066 CameraChangedConn = nil
4067 end
4068 local newCamera = workspace.CurrentCamera
4069 if newCamera then
4070 local size = newCamera.ViewportSize
4071 PortraitMode = size.X < size.Y
4072 layoutGestureArea(PortraitMode)
4073 DefaultZoom = PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM
4074 CameraChangedConn = newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
4075 size = newCamera.ViewportSize
4076 PortraitMode = size.X < size.Y
4077 layoutGestureArea(PortraitMode)
4078 DefaultZoom = PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM
4079 end)
4080 end
4081 end
4082 end
4083 workspaceCameraChangedConn = workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(onWorkspaceCameraChanged)
4084 if workspace.CurrentCamera then
4085 onWorkspaceCameraChanged()
4086 end
4087
4088
4089 function this:GetShiftLock()
4090 return ShiftLockController:IsShiftLocked()
4091 end
4092
4093 function this:GetHumanoid()
4094 local player = PlayersService.LocalPlayer
4095 return findPlayerHumanoid(player)
4096 end
4097
4098 function this:GetHumanoidRootPart()
4099 local humanoid = this:GetHumanoid()
4100 return humanoid and humanoid.Torso
4101 end
4102
4103 function this:GetRenderCFrame(part)
4104 GetRenderCFrame(part)
4105 end
4106
4107 local STATE_DEAD = Enum.HumanoidStateType.Dead
4108
4109 -- HumanoidRootPart when alive, Head part when dead
4110 local function getHumanoidPartToFollow(humanoid, humanoidStateType)
4111 if humanoidStateType == STATE_DEAD then
4112 local character = humanoid.Parent
4113 if character then
4114 return character:FindFirstChild("Head") or humanoid.Torso
4115 else
4116 return humanoid.Torso
4117 end
4118 else
4119 return humanoid.Torso
4120 end
4121 end
4122
4123 local HUMANOID_STATE_DEAD = Enum.HumanoidStateType.Dead
4124
4125 function this:GetSubjectPosition()
4126 local result = nil
4127 local camera = workspace.CurrentCamera
4128 local cameraSubject = camera and camera.CameraSubject
4129 if cameraSubject then
4130 if cameraSubject:IsA('Humanoid') then
4131 local humanoidStateType = cameraSubject:GetState()
4132 if VRService.VREnabled and humanoidStateType == STATE_DEAD and cameraSubject == this.lastSubject then
4133 result = this.lastSubjectPosition
4134 else
4135 local humanoidRootPart = getHumanoidPartToFollow(cameraSubject, humanoidStateType)
4136 if humanoidRootPart and humanoidRootPart:IsA('BasePart') then
4137 local subjectCFrame = GetRenderCFrame(humanoidRootPart)
4138 local heightOffset = ZERO_VECTOR3
4139 if humanoidStateType ~= STATE_DEAD then
4140 heightOffset = cameraSubject.RigType == Enum.HumanoidRigType.R15 and R15HeadHeight or HEAD_OFFSET
4141 end
4142
4143 result = subjectCFrame.p +
4144 subjectCFrame:vectorToWorldSpace(heightOffset + cameraSubject.CameraOffset)
4145 end
4146 end
4147 elseif cameraSubject:IsA('VehicleSeat') then
4148 local subjectCFrame = GetRenderCFrame(cameraSubject)
4149 local offset = SEAT_OFFSET
4150 if VRService.VREnabled then
4151 offset = VR_SEAT_OFFSET
4152 end
4153 result = subjectCFrame.p + subjectCFrame:vectorToWorldSpace(offset)
4154 elseif cameraSubject:IsA('SkateboardPlatform') then
4155 local subjectCFrame = GetRenderCFrame(cameraSubject)
4156 result = subjectCFrame.p + SEAT_OFFSET
4157 elseif cameraSubject:IsA('BasePart') then
4158 local subjectCFrame = GetRenderCFrame(cameraSubject)
4159 result = subjectCFrame.p
4160 elseif cameraSubject:IsA('Model') then
4161 result = cameraSubject:GetModelCFrame().p
4162 end
4163 end
4164 this.lastSubject = cameraSubject
4165 this.lastSubjectPosition = result
4166 return result
4167 end
4168
4169 function this:ResetCameraLook()
4170 end
4171
4172 function this:GetCameraLook()
4173 return workspace.CurrentCamera and workspace.CurrentCamera.CoordinateFrame.lookVector or Vector3.new(0,0,1)
4174 end
4175
4176 function this:GetCameraZoom()
4177 if this.currentZoom == nil then
4178 local player = PlayersService.LocalPlayer
4179 this.currentZoom = player and clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, this.DefaultZoom) or this.DefaultZoom
4180 end
4181 return this.currentZoom
4182 end
4183
4184 function this:GetCameraActualZoom()
4185 local camera = workspace.CurrentCamera
4186 if camera then
4187 return (camera.CoordinateFrame.p - camera.Focus.p).magnitude
4188 end
4189 end
4190
4191 function this:GetCameraHeight()
4192 if VRService.VREnabled and not this:IsInFirstPerson() then
4193 local zoom = this:GetCameraZoom()
4194 return math.sin(VR_ANGLE) * zoom
4195 end
4196 return 0
4197 end
4198
4199 function this:ViewSizeX()
4200 local result = 1024
4201 local camera = workspace.CurrentCamera
4202 if camera then
4203 result = camera.ViewportSize.X
4204 end
4205 return result
4206 end
4207
4208 function this:ViewSizeY()
4209 local result = 768
4210 local camera = workspace.CurrentCamera
4211 if camera then
4212 result = camera.ViewportSize.Y
4213 end
4214 return result
4215 end
4216
4217
4218 local math_asin = math.asin
4219 local math_atan2 = math.atan2
4220 local math_floor = math.floor
4221 local math_max = math.max
4222 local math_pi = math.pi
4223 local Vector2_new = Vector2.new
4224 local Vector3_new = Vector3.new
4225 local CFrame_Angles = CFrame.Angles
4226 local CFrame_new = CFrame.new
4227
4228 function this:ScreenTranslationToAngle(translationVector)
4229 local screenX = this:ViewSizeX()
4230 local screenY = this:ViewSizeY()
4231 local xTheta = (translationVector.x / screenX)
4232 local yTheta = (translationVector.y / screenY)
4233 return Vector2_new(xTheta, yTheta)
4234 end
4235
4236 function this:MouseTranslationToAngle(translationVector)
4237 local xTheta = (translationVector.x / 1920)
4238 local yTheta = (translationVector.y / 1200)
4239 return Vector2_new(xTheta, yTheta)
4240 end
4241
4242 function this:RotateVector(startVector, xyRotateVector)
4243 local startCFrame = CFrame_new(ZERO_VECTOR3, startVector)
4244 local resultLookVector = (CFrame_Angles(0, -xyRotateVector.x, 0) * startCFrame * CFrame_Angles(-xyRotateVector.y,0,0)).lookVector
4245 return resultLookVector, Vector2_new(xyRotateVector.x, xyRotateVector.y)
4246 end
4247
4248 function this:RotateCamera(startLook, xyRotateVector)
4249 if VRService.VREnabled then
4250 local yawRotatedVector, xyRotateVector = self:RotateVector(startLook, Vector2.new(xyRotateVector.x, 0))
4251 return Vector3_new(yawRotatedVector.x, 0, yawRotatedVector.z).unit, xyRotateVector
4252 else
4253 local startVertical = math_asin(startLook.y)
4254 local yTheta = clamp(-MAX_Y + startVertical, -MIN_Y + startVertical, xyRotateVector.y)
4255 return self:RotateVector(startLook, Vector2_new(xyRotateVector.x, yTheta))
4256 end
4257 end
4258
4259 function this:IsInFirstPerson()
4260 return isFirstPerson
4261 end
4262
4263 -- there are several cases to consider based on the state of input and camera rotation mode
4264 function this:UpdateMouseBehavior()
4265 -- first time transition to first person mode or shiftlock
4266 if isFirstPerson or self:GetShiftLock() then
4267 pcall(function() GameSettings.RotationType = Enum.RotationType.CameraRelative end)
4268 if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
4269 UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
4270 end
4271 else
4272 pcall(function() GameSettings.RotationType = Enum.RotationType.MovementRelative end)
4273 if isRightMouseDown or isMiddleMouseDown then
4274 UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
4275 else
4276 UserInputService.MouseBehavior = Enum.MouseBehavior.Default
4277 end
4278 end
4279 end
4280
4281 function this:ZoomCamera(desiredZoom)
4282 local player = PlayersService.LocalPlayer
4283 if player then
4284 if player.CameraMode == Enum.CameraMode.LockFirstPerson then
4285 this.currentZoom = 0
4286 else
4287 this.currentZoom = clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, desiredZoom)
4288 end
4289 end
4290
4291 isFirstPerson = self:GetCameraZoom() < 2
4292
4293 ShiftLockController:SetIsInFirstPerson(isFirstPerson)
4294 -- set mouse behavior
4295 self:UpdateMouseBehavior()
4296 return self:GetCameraZoom()
4297 end
4298
4299 function this:rk4Integrator(position, velocity, t)
4300 local direction = velocity < 0 and -1 or 1
4301 local function acceleration(p, v)
4302 local accel = direction * math_max(1, (p / 3.3) + 0.5)
4303 return accel
4304 end
4305
4306 local p1 = position
4307 local v1 = velocity
4308 local a1 = acceleration(p1, v1)
4309 local p2 = p1 + v1 * (t / 2)
4310 local v2 = v1 + a1 * (t / 2)
4311 local a2 = acceleration(p2, v2)
4312 local p3 = p1 + v2 * (t / 2)
4313 local v3 = v1 + a2 * (t / 2)
4314 local a3 = acceleration(p3, v3)
4315 local p4 = p1 + v3 * t
4316 local v4 = v1 + a3 * t
4317 local a4 = acceleration(p4, v4)
4318
4319 local positionResult = position + (v1 + 2 * v2 + 2 * v3 + v4) * (t / 6)
4320 local velocityResult = velocity + (a1 + 2 * a2 + 2 * a3 + a4) * (t / 6)
4321 return positionResult, velocityResult
4322 end
4323
4324 function this:ZoomCameraBy(zoomScale)
4325 local zoom = this:GetCameraActualZoom()
4326
4327 if zoom then
4328 -- Can break into more steps to get more accurate integration
4329 if UserSettings():IsUserFeatureEnabled("UserBetterInertialScrolling") then
4330 zoom = self:rk4Integrator(zoom, zoomScale, 1/10)
4331 else
4332 zoom = self:rk4Integrator(zoom, zoomScale, 1)
4333 end
4334 self:ZoomCamera(zoom)
4335 end
4336 return self:GetCameraZoom()
4337 end
4338
4339 function this:ZoomCameraFixedBy(zoomIncrement)
4340 return self:ZoomCamera(self:GetCameraZoom() + zoomIncrement)
4341 end
4342
4343 function this:Update()
4344 end
4345
4346 ----- VR STUFF ------
4347
4348 function this:ApplyVRTransform()
4349 if not VRService.VREnabled then
4350 return
4351 end
4352 --we only want this to happen in first person VR
4353 local player = PlayersService.LocalPlayer
4354 if not (player and player.Character
4355 and player.Character:FindFirstChild("HumanoidRootPart")
4356 and player.Character.HumanoidRootPart:FindFirstChild("RootJoint")) then
4357 return
4358 end
4359
4360 local camera = workspace.CurrentCamera
4361 local cameraSubject = camera.CameraSubject
4362 local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
4363
4364 if this:IsInFirstPerson() and not isInVehicle then
4365 local vrFrame = VRService:GetUserCFrame(Enum.UserCFrame.Head)
4366 local vrRotation = vrFrame - vrFrame.p
4367 local rootJoint = player.Character.HumanoidRootPart.RootJoint
4368 rootJoint.C0 = CFrame.new(vrRotation:vectorToObjectSpace(vrFrame.p)) * CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
4369 else
4370 local rootJoint = player.Character.HumanoidRootPart.RootJoint
4371 rootJoint.C0 = CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
4372 end
4373 end
4374
4375 local vrRotationIntensityExists = true
4376 local lastVrRotationCheck = 0
4377 function this:ShouldUseVRRotation()
4378 if not VRService.VREnabled then
4379 return false
4380 end
4381 if not vrRotationIntensityExists and tick() - lastVrRotationCheck < 1 then return false end
4382
4383 local success, vrRotationIntensity = pcall(function() return StarterGui:GetCore("VRRotationIntensity") end)
4384 vrRotationIntensityExists = success and vrRotationIntensity ~= nil
4385 lastVrRotationCheck = tick()
4386
4387 return success and vrRotationIntensity ~= nil and vrRotationIntensity ~= "Smooth"
4388 end
4389
4390 function this:GetVRRotationInput()
4391 local vrRotateSum = ZERO_VECTOR2
4392
4393 local vrRotationIntensity = StarterGui:GetCore("VRRotationIntensity")
4394
4395 local vrGamepadRotation = self.GamepadPanningCamera or ZERO_VECTOR2
4396 local delayExpired = (tick() - lastVRRotation) >= self:GetRepeatDelayValue(vrRotationIntensity)
4397
4398 if math.abs(vrGamepadRotation.x) >= self:GetActivateValue() then
4399 if (delayExpired or not vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2]) then
4400 local sign = 1
4401 if vrGamepadRotation.x < 0 then
4402 sign = -1
4403 end
4404 vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity) * sign
4405 vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = true
4406 end
4407 elseif math.abs(vrGamepadRotation.x) < self:GetActivateValue() - 0.1 then
4408 vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = nil
4409 end
4410 if self.TurningLeft then
4411 if delayExpired or not vrRotateKeyCooldown[Enum.KeyCode.Left] then
4412 vrRotateSum = vrRotateSum - self:GetRotateAmountValue(vrRotationIntensity)
4413 vrRotateKeyCooldown[Enum.KeyCode.Left] = true
4414 end
4415 else
4416 vrRotateKeyCooldown[Enum.KeyCode.Left] = nil
4417 end
4418 if self.TurningRight then
4419 if (delayExpired or not vrRotateKeyCooldown[Enum.KeyCode.Right]) then
4420 vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity)
4421 vrRotateKeyCooldown[Enum.KeyCode.Right] = true
4422 end
4423 else
4424 vrRotateKeyCooldown[Enum.KeyCode.Right] = nil
4425 end
4426
4427 if vrRotateSum ~= ZERO_VECTOR2 then
4428 lastVRRotation = tick()
4429 end
4430
4431 return vrRotateSum
4432 end
4433
4434 local cameraTranslationConstraints = Vector3.new(1, 1, 1)
4435 local humanoidJumpOrigin = nil
4436 local trackingHumanoid = nil
4437 local cameraFrozen = false
4438 local subjectStateChangedConn = nil
4439 local cameraSubjectChangedConn = nil
4440 local workspaceChangedConn = nil
4441 local humanoidChildAddedConn = nil
4442 local humanoidChildRemovedConn = nil
4443
4444 local function cancelCameraFreeze(keepConstraints)
4445 if not keepConstraints then
4446 cameraTranslationConstraints = Vector3.new(cameraTranslationConstraints.x, 1, cameraTranslationConstraints.z)
4447 end
4448 if cameraFrozen then
4449 trackingHumanoid = nil
4450 cameraFrozen = false
4451 end
4452 end
4453
4454 local function startCameraFreeze(subjectPosition, humanoidToTrack)
4455 if not cameraFrozen then
4456 humanoidJumpOrigin = subjectPosition
4457 trackingHumanoid = humanoidToTrack
4458 cameraTranslationConstraints = Vector3.new(cameraTranslationConstraints.x, 0, cameraTranslationConstraints.z)
4459 cameraFrozen = true
4460 end
4461 end
4462
4463 local function rescaleCameraOffset(newScaleFactor)
4464 R15HeadHeight = R15_HEAD_OFFSET*newScaleFactor
4465 end
4466
4467 local function onHumanoidSubjectChildAdded(child)
4468 if child.Name == "BodyHeightScale" and child:IsA("NumberValue") then
4469 if heightScaleChangedConn then
4470 heightScaleChangedConn:disconnect()
4471 end
4472 heightScaleChangedConn = child.Changed:connect(rescaleCameraOffset)
4473 rescaleCameraOffset(child.Value)
4474 end
4475 end
4476
4477 local function onHumanoidSubjectChildRemoved(child)
4478 if child.Name == "BodyHeightScale" then
4479 rescaleCameraOffset(1)
4480 if heightScaleChangedConn then
4481 heightScaleChangedConn:disconnect()
4482 heightScaleChangedConn = nil
4483 end
4484 end
4485 end
4486
4487 local function onNewCameraSubject()
4488 if subjectStateChangedConn then
4489 subjectStateChangedConn:disconnect()
4490 subjectStateChangedConn = nil
4491 end
4492 if humanoidChildAddedConn then
4493 humanoidChildAddedConn:disconnect()
4494 humanoidChildAddedConn = nil
4495 end
4496 if humanoidChildRemovedConn then
4497 humanoidChildRemovedConn:disconnect()
4498 humanoidChildRemovedConn = nil
4499 end
4500 if heightScaleChangedConn then
4501 heightScaleChangedConn:disconnect()
4502 heightScaleChangedConn = nil
4503 end
4504
4505 local humanoid = workspace.CurrentCamera and workspace.CurrentCamera.CameraSubject
4506 if trackingHumanoid ~= humanoid then
4507 cancelCameraFreeze()
4508 end
4509 if humanoid and humanoid:IsA('Humanoid') then
4510 humanoidChildAddedConn = humanoid.ChildAdded:connect(onHumanoidSubjectChildAdded)
4511 humanoidChildRemovedConn = humanoid.ChildRemoved:connect(onHumanoidSubjectChildRemoved)
4512 for _, child in pairs(humanoid:GetChildren()) do
4513 onHumanoidSubjectChildAdded(child)
4514 end
4515
4516 subjectStateChangedConn = humanoid.StateChanged:connect(function(oldState, newState)
4517 if VRService.VREnabled and newState == Enum.HumanoidStateType.Jumping and not this:IsInFirstPerson() then
4518 startCameraFreeze(this:GetSubjectPosition(), humanoid)
4519 elseif newState ~= Enum.HumanoidStateType.Jumping and newState ~= Enum.HumanoidStateType.Freefall then
4520 cancelCameraFreeze(true)
4521 end
4522 end)
4523 end
4524 end
4525
4526 local function onCurrentCameraChanged()
4527 if cameraSubjectChangedConn then
4528 cameraSubjectChangedConn:disconnect()
4529 cameraSubjectChangedConn = nil
4530 end
4531 local camera = workspace.CurrentCamera
4532 if camera then
4533 cameraSubjectChangedConn = camera:GetPropertyChangedSignal("CameraSubject"):connect(onNewCameraSubject)
4534 onNewCameraSubject()
4535 end
4536 end
4537
4538 function this:GetVRFocus(subjectPosition, timeDelta)
4539 local newFocus = nil
4540
4541 local camera = workspace.CurrentCamera
4542 local lastFocus = self.LastCameraFocus or subjectPosition
4543 if not cameraFrozen then
4544 cameraTranslationConstraints = Vector3.new(cameraTranslationConstraints.x, math.min(1, cameraTranslationConstraints.y + 0.42 * timeDelta), cameraTranslationConstraints.z)
4545 end
4546 if cameraFrozen and humanoidJumpOrigin and humanoidJumpOrigin.y > lastFocus.y then
4547 newFocus = CFrame.new(Vector3.new(subjectPosition.x, math.min(humanoidJumpOrigin.y, lastFocus.y + 5 * timeDelta), subjectPosition.z))
4548 else
4549 newFocus = CFrame.new(Vector3.new(subjectPosition.x, lastFocus.y, subjectPosition.z):lerp(subjectPosition, cameraTranslationConstraints.y))
4550 end
4551
4552 if cameraFrozen then
4553 -- No longer in 3rd person
4554 if self:IsInFirstPerson() then -- not VRService.VREnabled
4555 cancelCameraFreeze()
4556 end
4557 -- This case you jumped off a cliff and want to keep your character in view
4558 -- 0.5 is to fix floating point error when not jumping off cliffs
4559 if humanoidJumpOrigin and subjectPosition.y < (humanoidJumpOrigin.y - 0.5) then
4560 cancelCameraFreeze()
4561 end
4562 end
4563
4564 return newFocus
4565 end
4566
4567 ------------------------
4568
4569 ---- Input Events ----
4570 local startPos = nil
4571 local lastPos = nil
4572 local panBeginLook = nil
4573 local lastTapTime = nil
4574
4575 local fingerTouches = {}
4576 local NumUnsunkTouches = 0
4577
4578 local inputStartPositions = {}
4579 local inputStartTimes = {}
4580
4581 local StartingDiff = nil
4582 local pinchBeginZoom = nil
4583
4584 this.ZoomEnabled = true
4585 this.PanEnabled = true
4586 this.KeyPanEnabled = true
4587
4588 local function OnTouchBegan(input, processed)
4589 --If isDynamicThumbstickEnabled, then only process TouchBegan event if it starts in GestureArea
4590 if (not touchWorkspaceEventEnabled and not isDynamicThumbstickEnabled) or positionIntersectsGuiObject(input.Position, GestureArea) then
4591 fingerTouches[input] = processed
4592 if not processed then
4593 inputStartPositions[input] = input.Position
4594 inputStartTimes[input] = tick()
4595 NumUnsunkTouches = NumUnsunkTouches + 1
4596 end
4597 end
4598 end
4599
4600 local function OnTouchChanged(input, processed)
4601
4602 if fingerTouches[input] == nil then
4603 if isDynamicThumbstickEnabled then
4604 return
4605 end
4606 fingerTouches[input] = processed
4607 if not processed then
4608 NumUnsunkTouches = NumUnsunkTouches + 1
4609 end
4610 end
4611
4612 if NumUnsunkTouches == 1 then
4613 if fingerTouches[input] == false then
4614 panBeginLook = panBeginLook or this:GetCameraLook()
4615 startPos = startPos or input.Position
4616 lastPos = lastPos or startPos
4617 this.UserPanningTheCamera = true
4618
4619 local delta = input.Position - lastPos
4620
4621 delta = Vector2.new(delta.X, delta.Y * GameSettings:GetCameraYInvertValue())
4622
4623 if this.PanEnabled then
4624 local desiredXYVector = this:ScreenTranslationToAngle(delta) * TOUCH_SENSITIVTY
4625 this.RotateInput = this.RotateInput + desiredXYVector
4626 end
4627
4628 lastPos = input.Position
4629 end
4630 else
4631 panBeginLook = nil
4632 startPos = nil
4633 lastPos = nil
4634 this.UserPanningTheCamera = false
4635 end
4636 if NumUnsunkTouches == 2 then
4637 local unsunkTouches = {}
4638 for touch, wasSunk in pairs(fingerTouches) do
4639 if not wasSunk then
4640 table.insert(unsunkTouches, touch)
4641 end
4642 end
4643 if #unsunkTouches == 2 then
4644 local difference = (unsunkTouches[1].Position - unsunkTouches[2].Position).magnitude
4645 if StartingDiff and pinchBeginZoom then
4646 local scale = difference / math_max(0.01, StartingDiff)
4647 local clampedScale = clamp(0.1, 10, scale)
4648 if this.ZoomEnabled then
4649 this:ZoomCamera(pinchBeginZoom / clampedScale)
4650 end
4651 else
4652 StartingDiff = difference
4653 pinchBeginZoom = this:GetCameraActualZoom()
4654 end
4655 end
4656 else
4657 StartingDiff = nil
4658 pinchBeginZoom = nil
4659 end
4660 end
4661
4662 local function calcLookBehindRotateInput(torso)
4663 if torso then
4664 local newDesiredLook = (torso.CFrame.lookVector - Vector3.new(0,0.23,0)).unit
4665 local horizontalShift = findAngleBetweenXZVectors(newDesiredLook, this:GetCameraLook())
4666 local vertShift = math.asin(this:GetCameraLook().y) - math.asin(newDesiredLook.y)
4667 if not IsFinite(horizontalShift) then
4668 horizontalShift = 0
4669 end
4670 if not IsFinite(vertShift) then
4671 vertShift = 0
4672 end
4673
4674 return Vector2.new(horizontalShift, vertShift)
4675 end
4676 return nil
4677 end
4678
4679 local OnTouchTap = nil
4680 if not touchWorkspaceEventEnabled then
4681 OnTouchTap = function(position)
4682 if isDynamicThumbstickEnabled and not IsAToolEquipped then
4683 if lastTapTime and tick() - lastTapTime < MAX_TIME_FOR_DOUBLE_TAP then
4684 local tween = {
4685 from = this:GetCameraZoom(),
4686 to = DefaultZoom,
4687 start = tick(),
4688 duration = 0.2,
4689 func = function(from, to, alpha)
4690 this:ZoomCamera(from + (to - from)*alpha)
4691 return to
4692 end
4693 }
4694 tweens["Zoom"] = tween
4695 else
4696 local humanoid = this:GetHumanoid()
4697 if humanoid then
4698 local player = PlayersService.LocalPlayer
4699 if player and player.Character then
4700 if humanoid and humanoid.Torso then
4701 local tween = {
4702 from = this.RotateInput,
4703 to = calcLookBehindRotateInput(humanoid.Torso),
4704 start = tick(),
4705 duration = 0.2,
4706 func = function(from, to, alpha)
4707 to = calcLookBehindRotateInput(humanoid.Torso)
4708 if to then
4709 this.RotateInput = from + (to - from)*alpha
4710 end
4711 return to
4712 end
4713 }
4714 tweens["Rotate"] = tween
4715
4716 -- reset old camera info so follow cam doesn't rotate us
4717 this.LastCameraTransform = nil
4718 end
4719 end
4720 end
4721 end
4722 lastTapTime = tick()
4723 end
4724 end
4725 end
4726
4727 local function IsTouchTap(input)
4728 -- We can't make the assumption that the input exists in the inputStartPositions because we may have switched from a different camera type.
4729 if inputStartPositions[input] then
4730 local posDelta = (inputStartPositions[input] - input.Position).magnitude
4731 if posDelta < MAX_TAP_POS_DELTA then
4732 local timeDelta = inputStartTimes[input] - tick()
4733 if timeDelta < MAX_TAP_TIME_DELTA then
4734 return true
4735 end
4736 end
4737 end
4738 return false
4739 end
4740
4741
4742 local function OnTouchEnded(input, processed)
4743 if fingerTouches[input] == false then
4744 if NumUnsunkTouches == 1 then
4745 panBeginLook = nil
4746 startPos = nil
4747 lastPos = nil
4748 this.UserPanningTheCamera = false
4749 if not touchWorkspaceEventEnabled and IsTouchTap(input) then
4750 OnTouchTap(input.Position)
4751 end
4752 elseif NumUnsunkTouches == 2 then
4753 StartingDiff = nil
4754 pinchBeginZoom = nil
4755 end
4756 end
4757
4758 if fingerTouches[input] ~= nil and fingerTouches[input] == false then
4759 NumUnsunkTouches = NumUnsunkTouches - 1
4760 end
4761 fingerTouches[input] = nil
4762 inputStartPositions[input] = nil
4763 inputStartTimes[input] = nil
4764 end
4765
4766 local function OnMousePanButtonPressed(input, processed)
4767 if processed then return end
4768 this:UpdateMouseBehavior()
4769 panBeginLook = panBeginLook or this:GetCameraLook()
4770 startPos = startPos or input.Position
4771 lastPos = lastPos or startPos
4772 this.UserPanningTheCamera = true
4773 end
4774
4775 local function OnMousePanButtonReleased(input, processed)
4776 this:UpdateMouseBehavior()
4777 if not (isRightMouseDown or isMiddleMouseDown) then
4778 panBeginLook = nil
4779 startPos = nil
4780 lastPos = nil
4781 this.UserPanningTheCamera = false
4782 end
4783 end
4784
4785 local function OnMouse2Down(input, processed)
4786 if processed then return end
4787
4788 isRightMouseDown = true
4789 OnMousePanButtonPressed(input, processed)
4790 end
4791
4792 local function OnMouse2Up(input, processed)
4793 isRightMouseDown = false
4794 OnMousePanButtonReleased(input, processed)
4795 end
4796
4797 local function OnMouse3Down(input, processed)
4798 if processed then return end
4799
4800 isMiddleMouseDown = true
4801 OnMousePanButtonPressed(input, processed)
4802 end
4803
4804 local function OnMouse3Up(input, processed)
4805 isMiddleMouseDown = false
4806 OnMousePanButtonReleased(input, processed)
4807 end
4808
4809 local function OnMouseMoved(input, processed)
4810 if not hasGameLoaded and VRService.VREnabled then
4811 return
4812 end
4813
4814 local inputDelta = input.Delta
4815 inputDelta = Vector2.new(inputDelta.X, inputDelta.Y * GameSettings:GetCameraYInvertValue())
4816
4817 if startPos and lastPos and panBeginLook then
4818 local currPos = lastPos + input.Delta
4819 local totalTrans = currPos - startPos
4820 if this.PanEnabled then
4821 local desiredXYVector = this:MouseTranslationToAngle(inputDelta) * MOUSE_SENSITIVITY
4822 this.RotateInput = this.RotateInput + desiredXYVector
4823 end
4824 lastPos = currPos
4825 elseif this:IsInFirstPerson() or this:GetShiftLock() then
4826 if this.PanEnabled then
4827 local desiredXYVector = this:MouseTranslationToAngle(inputDelta) * MOUSE_SENSITIVITY
4828 this.RotateInput = this.RotateInput + desiredXYVector
4829 end
4830 end
4831 end
4832
4833 local function OnMouseWheel(input, processed)
4834 if not hasGameLoaded and VRService.VREnabled then
4835 return
4836 end
4837 if not processed then
4838 if this.ZoomEnabled then
4839 if UserSettings():IsUserFeatureEnabled("UserBetterInertialScrolling") then
4840 this:ZoomCameraBy(-input.Position.Z/15)
4841 else
4842 this:ZoomCameraBy(clamp(-1, 1, -input.Position.Z) * 1.4)
4843 end
4844 end
4845 end
4846 end
4847
4848 local function round(num)
4849 return math_floor(num + 0.5)
4850 end
4851
4852 local eight2Pi = math_pi / 4
4853
4854 local function rotateVectorByAngleAndRound(camLook, rotateAngle, roundAmount)
4855 if camLook ~= ZERO_VECTOR3 then
4856 camLook = camLook.unit
4857 local currAngle = math_atan2(camLook.z, camLook.x)
4858 local newAngle = round((math_atan2(camLook.z, camLook.x) + rotateAngle) / roundAmount) * roundAmount
4859 return newAngle - currAngle
4860 end
4861 return 0
4862 end
4863
4864 local function OnKeyDown(input, processed)
4865 if not hasGameLoaded and VRService.VREnabled then
4866 return
4867 end
4868 if processed then return end
4869 if this.ZoomEnabled then
4870 if input.KeyCode == Enum.KeyCode.I then
4871 this:ZoomCameraBy(-5)
4872 elseif input.KeyCode == Enum.KeyCode.O then
4873 this:ZoomCameraBy(5)
4874 end
4875 end
4876 if panBeginLook == nil and this.KeyPanEnabled then
4877 if input.KeyCode == Enum.KeyCode.Left then
4878 this.TurningLeft = true
4879 elseif input.KeyCode == Enum.KeyCode.Right then
4880 this.TurningRight = true
4881 elseif input.KeyCode == Enum.KeyCode.Comma then
4882 local angle = rotateVectorByAngleAndRound(this:GetCameraLook() * Vector3.new(1,0,1), -eight2Pi * (3/4), eight2Pi)
4883 if angle ~= 0 then
4884 this.RotateInput = this.RotateInput + Vector2.new(angle, 0)
4885 this.LastUserPanCamera = tick()
4886 this.LastCameraTransform = nil
4887 end
4888 elseif input.KeyCode == Enum.KeyCode.Period then
4889 local angle = rotateVectorByAngleAndRound(this:GetCameraLook() * Vector3.new(1,0,1), eight2Pi * (3/4), eight2Pi)
4890 if angle ~= 0 then
4891 this.RotateInput = this.RotateInput + Vector2.new(angle, 0)
4892 this.LastUserPanCamera = tick()
4893 this.LastCameraTransform = nil
4894 end
4895 elseif input.KeyCode == Enum.KeyCode.PageUp then
4896 --elseif input.KeyCode == Enum.KeyCode.Home then
4897 this.RotateInput = this.RotateInput + Vector2.new(0,math.rad(15))
4898 this.LastCameraTransform = nil
4899 elseif input.KeyCode == Enum.KeyCode.PageDown then
4900 --elseif input.KeyCode == Enum.KeyCode.End then
4901 this.RotateInput = this.RotateInput + Vector2.new(0,math.rad(-15))
4902 this.LastCameraTransform = nil
4903 end
4904 end
4905 end
4906
4907 local function OnKeyUp(input, processed)
4908 if input.KeyCode == Enum.KeyCode.Left then
4909 this.TurningLeft = false
4910 elseif input.KeyCode == Enum.KeyCode.Right then
4911 this.TurningRight = false
4912 end
4913 end
4914
4915 local lastThumbstickRotate = nil
4916 local numOfSeconds = 0.7
4917 local currentSpeed = 0
4918 local maxSpeed = 6
4919 local vrMaxSpeed = 4
4920 local lastThumbstickPos = Vector2.new(0,0)
4921 local ySensitivity = 0.65
4922 local lastVelocity = nil
4923
4924 -- K is a tunable parameter that changes the shape of the S-curve
4925 -- the larger K is the more straight/linear the curve gets
4926 local k = 0.35
4927 local lowerK = 0.8
4928 local function SCurveTranform(t)
4929 t = clamp(-1,1,t)
4930 if t >= 0 then
4931 return (k*t) / (k - t + 1)
4932 end
4933 return -((lowerK*-t) / (lowerK + t + 1))
4934 end
4935
4936 -- DEADZONE
4937 local DEADZONE = 0.1
4938 local function toSCurveSpace(t)
4939 return (1 + DEADZONE) * (2*math.abs(t) - 1) - DEADZONE
4940 end
4941
4942 local function fromSCurveSpace(t)
4943 return t/2 + 0.5
4944 end
4945
4946 local function gamepadLinearToCurve(thumbstickPosition)
4947 local function onAxis(axisValue)
4948 local sign = 1
4949 if axisValue < 0 then
4950 sign = -1
4951 end
4952 local point = fromSCurveSpace(SCurveTranform(toSCurveSpace(math.abs(axisValue))))
4953 point = point * sign
4954 return clamp(-1, 1, point)
4955 end
4956 return Vector2_new(onAxis(thumbstickPosition.x), onAxis(thumbstickPosition.y))
4957 end
4958
4959 function this:UpdateGamepad()
4960 local gamepadPan = this.GamepadPanningCamera
4961 if gamepadPan and (hasGameLoaded or not VRService.VREnabled) then
4962 gamepadPan = gamepadLinearToCurve(gamepadPan)
4963 local currentTime = tick()
4964 if gamepadPan.X ~= 0 or gamepadPan.Y ~= 0 then
4965 this.userPanningTheCamera = true
4966 elseif gamepadPan == ZERO_VECTOR2 then
4967 lastThumbstickRotate = nil
4968 if lastThumbstickPos == ZERO_VECTOR2 then
4969 currentSpeed = 0
4970 end
4971 end
4972
4973 local finalConstant = 0
4974
4975 if lastThumbstickRotate then
4976 if VRService.VREnabled then
4977 currentSpeed = vrMaxSpeed
4978 else
4979 local elapsedTime = (currentTime - lastThumbstickRotate) * 10
4980 currentSpeed = currentSpeed + (maxSpeed * ((elapsedTime*elapsedTime)/numOfSeconds))
4981
4982 if currentSpeed > maxSpeed then currentSpeed = maxSpeed end
4983
4984 if lastVelocity then
4985 local velocity = (gamepadPan - lastThumbstickPos)/(currentTime - lastThumbstickRotate)
4986 local velocityDeltaMag = (velocity - lastVelocity).magnitude
4987
4988 if velocityDeltaMag > 12 then
4989 currentSpeed = currentSpeed * (20/velocityDeltaMag)
4990 if currentSpeed > maxSpeed then currentSpeed = maxSpeed end
4991 end
4992 end
4993 end
4994
4995 local success, gamepadCameraSensitivity = pcall(function() return GameSettings.GamepadCameraSensitivity end)
4996 finalConstant = success and (gamepadCameraSensitivity * currentSpeed) or currentSpeed
4997 lastVelocity = (gamepadPan - lastThumbstickPos)/(currentTime - lastThumbstickRotate)
4998 end
4999
5000 lastThumbstickPos = gamepadPan
5001 lastThumbstickRotate = currentTime
5002
5003 return Vector2_new( gamepadPan.X * finalConstant, gamepadPan.Y * finalConstant * ySensitivity * GameSettings:GetCameraYInvertValue())
5004 end
5005
5006 return ZERO_VECTOR2
5007 end
5008
5009 local InputBeganConn, InputChangedConn, InputEndedConn, MenuOpenedConn, ShiftLockToggleConn, GamepadConnectedConn, GamepadDisconnectedConn, TouchActivateConn = nil, nil, nil, nil, nil, nil, nil, nil
5010
5011 function this:DisconnectInputEvents()
5012 if InputBeganConn then
5013 InputBeganConn:disconnect()
5014 InputBeganConn = nil
5015 end
5016 if InputChangedConn then
5017 InputChangedConn:disconnect()
5018 InputChangedConn = nil
5019 end
5020 if InputEndedConn then
5021 InputEndedConn:disconnect()
5022 InputEndedConn = nil
5023 end
5024 if MenuOpenedConn then
5025 MenuOpenedConn:disconnect()
5026 MenuOpenedConn = nil
5027 end
5028 if ShiftLockToggleConn then
5029 ShiftLockToggleConn:disconnect()
5030 ShiftLockToggleConn = nil
5031 end
5032 if GamepadConnectedConn then
5033 GamepadConnectedConn:disconnect()
5034 GamepadConnectedConn = nil
5035 end
5036 if GamepadDisconnectedConn then
5037 GamepadDisconnectedConn:disconnect()
5038 GamepadDisconnectedConn = nil
5039 end
5040 if subjectStateChangedConn then
5041 subjectStateChangedConn:disconnect()
5042 subjectStateChangedConn = nil
5043 end
5044 if cameraChangedConn then
5045 cameraChangedConn:disconnect()
5046 cameraChangedConn = nil
5047 end
5048 if workspaceChangedConn then
5049 workspaceChangedConn:disconnect()
5050 workspaceChangedConn = nil
5051 end
5052 if TouchActivateConn then
5053 TouchActivateConn:disconnect()
5054 TouchActivateConn = nil
5055 end
5056
5057 this.TurningLeft = false
5058 this.TurningRight = false
5059 this.LastCameraTransform = nil
5060 self.LastSubjectCFrame = nil
5061 this.UserPanningTheCamera = false
5062 this.RotateInput = Vector2.new()
5063 this.GamepadPanningCamera = Vector2.new(0,0)
5064
5065 -- Reset input states
5066 startPos = nil
5067 lastPos = nil
5068 panBeginLook = nil
5069 isRightMouseDown = false
5070 isMiddleMouseDown = false
5071
5072 fingerTouches = {}
5073 NumUnsunkTouches = 0
5074
5075 StartingDiff = nil
5076 pinchBeginZoom = nil
5077
5078 -- Unlock mouse for example if right mouse button was being held down
5079 if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
5080 UserInputService.MouseBehavior = Enum.MouseBehavior.Default
5081 end
5082 end
5083
5084 function this:ResetInputStates()
5085 isRightMouseDown = false
5086 isMiddleMouseDown = false
5087 OnMousePanButtonReleased() -- this function doesn't seem to actually need parameters
5088
5089 if UserInputService.TouchEnabled then
5090 --[[menu opening was causing serious touch issues
5091 this should disable all active touch events if
5092 they're active when menu opens.]]
5093 for inputObject, value in pairs(fingerTouches) do
5094 fingerTouches[inputObject] = nil
5095 end
5096 panBeginLook = nil
5097 startPos = nil
5098 lastPos = nil
5099 this.UserPanningTheCamera = false
5100 StartingDiff = nil
5101 pinchBeginZoom = nil
5102 NumUnsunkTouches = 0
5103 end
5104 end
5105
5106 function this.getGamepadPan(name, state, input)
5107 if input.UserInputType == this.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
5108
5109 if state == Enum.UserInputState.Cancel then
5110 this.GamepadPanningCamera = ZERO_VECTOR2
5111 return
5112 end
5113
5114 local inputVector = Vector2.new(input.Position.X, -input.Position.Y)
5115 if inputVector.magnitude > THUMBSTICK_DEADZONE then
5116 this.GamepadPanningCamera = Vector2_new(input.Position.X, -input.Position.Y)
5117 else
5118 this.GamepadPanningCamera = ZERO_VECTOR2
5119 end
5120 end
5121 end
5122
5123 function this.doGamepadZoom(name, state, input)
5124 if input.UserInputType == this.activeGamepad and input.KeyCode == Enum.KeyCode.ButtonR3 and state == Enum.UserInputState.Begin then
5125 if this.ZoomEnabled then
5126 if this:GetCameraZoom() > 0.5 then
5127 this:ZoomCamera(0)
5128 else
5129 this:ZoomCamera(10)
5130 end
5131 end
5132 end
5133 end
5134
5135 function this:BindGamepadInputActions()
5136 ContextActionService:BindAction("RootCamGamepadPan", this.getGamepadPan, false, Enum.KeyCode.Thumbstick2)
5137 ContextActionService:BindAction("RootCamGamepadZoom", this.doGamepadZoom, false, Enum.KeyCode.ButtonR3)
5138 end
5139
5140 function this:ConnectInputEvents()
5141 InputBeganConn = UserInputService.InputBegan:connect(function(input, processed)
5142 if input.UserInputType == Enum.UserInputType.Touch then
5143 OnTouchBegan(input, processed)
5144 elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
5145 OnMouse2Down(input, processed)
5146 elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
5147 OnMouse3Down(input, processed)
5148 end
5149 -- Keyboard
5150 if input.UserInputType == Enum.UserInputType.Keyboard then
5151 OnKeyDown(input, processed)
5152 end
5153 end)
5154
5155 InputChangedConn = UserInputService.InputChanged:connect(function(input, processed)
5156 if input.UserInputType == Enum.UserInputType.Touch then
5157 OnTouchChanged(input, processed)
5158 elseif input.UserInputType == Enum.UserInputType.MouseMovement then
5159 OnMouseMoved(input, processed)
5160 elseif input.UserInputType == Enum.UserInputType.MouseWheel then
5161 OnMouseWheel(input, processed)
5162 end
5163 end)
5164
5165 InputEndedConn = UserInputService.InputEnded:connect(function(input, processed)
5166 if input.UserInputType == Enum.UserInputType.Touch then
5167 OnTouchEnded(input, processed)
5168 elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
5169 OnMouse2Up(input, processed)
5170 elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
5171 OnMouse3Up(input, processed)
5172 end
5173 -- Keyboard
5174 if input.UserInputType == Enum.UserInputType.Keyboard then
5175 OnKeyUp(input, processed)
5176 end
5177 end)
5178
5179 if touchWorkspaceEventEnabled then
5180 TouchActivateConn = UserInputService.TouchTapInWorld:connect(function(touchPos, processed)
5181 if isDynamicThumbstickEnabled and not processed and not IsAToolEquipped and positionIntersectsGuiObject(touchPos, GestureArea) then
5182 if lastTapTime and tick() - lastTapTime < MAX_TIME_FOR_DOUBLE_TAP then
5183 local tween = {
5184 from = this:GetCameraZoom(),
5185 to = DefaultZoom,
5186 start = tick(),
5187 duration = 0.2,
5188 func = function(from, to, alpha)
5189 this:ZoomCamera(from + (to - from)*alpha)
5190 return to
5191 end
5192 }
5193 tweens["Zoom"] = tween
5194 else
5195 local humanoid = this:GetHumanoid()
5196 if humanoid then
5197 local player = PlayersService.LocalPlayer
5198 if player and player.Character then
5199 if humanoid and humanoid.Torso then
5200 local tween = {
5201 from = this.RotateInput,
5202 to = calcLookBehindRotateInput(humanoid.Torso),
5203 start = tick(),
5204 duration = 0.2,
5205 func = function(from, to, alpha)
5206 to = calcLookBehindRotateInput(humanoid.Torso)
5207 if to then
5208 this.RotateInput = from + (to - from)*alpha
5209 end
5210 return to
5211 end
5212 }
5213 tweens["Rotate"] = tween
5214
5215 -- reset old camera info so follow cam doesn't rotate us
5216 this.LastCameraTransform = nil
5217 end
5218 end
5219 end
5220 end
5221 lastTapTime = tick()
5222 end
5223 end)
5224 end
5225
5226 MenuOpenedConn = GuiService.MenuOpened:connect(function()
5227 this:ResetInputStates()
5228 end)
5229
5230 workspaceChangedConn = workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(onCurrentCameraChanged)
5231 if workspace.CurrentCamera then
5232 onCurrentCameraChanged()
5233 end
5234
5235 ShiftLockToggleConn = ShiftLockController.OnShiftLockToggled.Event:connect(function()
5236 this:UpdateMouseBehavior()
5237 end)
5238
5239 this.RotateInput = Vector2.new()
5240
5241 this.activeGamepad = nil
5242 local function assignActivateGamepad()
5243 local connectedGamepads = UserInputService:GetConnectedGamepads()
5244 if #connectedGamepads > 0 then
5245 for i = 1, #connectedGamepads do
5246 if this.activeGamepad == nil then
5247 this.activeGamepad = connectedGamepads[i]
5248 elseif connectedGamepads[i].Value < this.activeGamepad.Value then
5249 this.activeGamepad = connectedGamepads[i]
5250 end
5251 end
5252 end
5253
5254 if this.activeGamepad == nil then -- nothing is connected, at least set up for gamepad1
5255 this.activeGamepad = Enum.UserInputType.Gamepad1
5256 end
5257 end
5258
5259 GamepadConnectedConn = UserInputService.GamepadDisconnected:connect(function(gamepadEnum)
5260 if this.activeGamepad ~= gamepadEnum then return end
5261 this.activeGamepad = nil
5262 assignActivateGamepad()
5263 end)
5264
5265 GamepadDisconnectedConn = UserInputService.GamepadConnected:connect(function(gamepadEnum)
5266 if this.activeGamepad == nil then
5267 assignActivateGamepad()
5268 end
5269 end)
5270
5271 self:BindGamepadInputActions()
5272
5273 assignActivateGamepad()
5274
5275 -- set mouse behavior
5276 self:UpdateMouseBehavior()
5277 end
5278
5279 --Process tweens related to tap-to-recenter and double-tap-to-zoom
5280 --Needs to be called from specific cameras on each update
5281 function this:ProcessTweens()
5282 for name, tween in pairs(tweens) do
5283 local alpha = math.min(1.0, (tick() - tween.start)/tween.duration)
5284 tween.to = tween.func(tween.from, tween.to, alpha)
5285 if math.abs(1 - alpha) < 0.0001 then
5286 tweens[name] = nil
5287 end
5288 end
5289 end
5290
5291 function this:SetEnabled(newState)
5292 if newState ~= self.Enabled then
5293 self.Enabled = newState
5294 if self.Enabled then
5295 self:ConnectInputEvents()
5296 self.cframe = workspace.CurrentCamera.CFrame
5297 else
5298 self:DisconnectInputEvents()
5299 end
5300 end
5301 end
5302
5303 local function OnPlayerAdded(player)
5304 player.Changed:connect(function(prop)
5305 if this.Enabled then
5306 if prop == "CameraMode" or prop == "CameraMaxZoomDistance" or prop == "CameraMinZoomDistance" then
5307 this:ZoomCameraFixedBy(0)
5308 end
5309 end
5310 end)
5311
5312 local function OnCharacterAdded(newCharacter)
5313
5314 local humanoid = findPlayerHumanoid(player)
5315 local start = tick()
5316 while tick() - start < 0.3 and (humanoid == nil or humanoid.Torso == nil) do
5317 wait()
5318 humanoid = findPlayerHumanoid(player)
5319 end
5320
5321 if humanoid and humanoid.Torso and player.Character == newCharacter then
5322 local newDesiredLook = (humanoid.Torso.CFrame.lookVector - Vector3.new(0,0.23,0)).unit
5323 local horizontalShift = findAngleBetweenXZVectors(newDesiredLook, this:GetCameraLook())
5324 local vertShift = math.asin(this:GetCameraLook().y) - math.asin(newDesiredLook.y)
5325 if not IsFinite(horizontalShift) then
5326 horizontalShift = 0
5327 end
5328 if not IsFinite(vertShift) then
5329 vertShift = 0
5330 end
5331 this.RotateInput = Vector2.new(horizontalShift, vertShift)
5332
5333 -- reset old camera info so follow cam doesn't rotate us
5334 this.LastCameraTransform = nil
5335 end
5336
5337 -- Need to wait for camera cframe to update before we zoom in
5338 -- Not waiting will force camera to original cframe
5339 wait()
5340 this:ZoomCamera(this.DefaultZoom)
5341 end
5342
5343 player.CharacterAdded:connect(function(character)
5344 if this.Enabled or SetCameraOnSpawn then
5345 OnCharacterAdded(character)
5346 SetCameraOnSpawn = false
5347 end
5348 end)
5349 if player.Character then
5350 spawn(function() OnCharacterAdded(player.Character) end)
5351 end
5352 end
5353 if PlayersService.LocalPlayer then
5354 OnPlayerAdded(PlayersService.LocalPlayer)
5355 end
5356 PlayersService.ChildAdded:connect(function(child)
5357 if child and PlayersService.LocalPlayer == child then
5358 OnPlayerAdded(PlayersService.LocalPlayer)
5359 end
5360 end)
5361
5362 local function OnGameLoaded()
5363 hasGameLoaded = true
5364 end
5365
5366 spawn(function()
5367 if game:IsLoaded() then
5368 OnGameLoaded()
5369 else
5370 game.Loaded:wait()
5371 OnGameLoaded()
5372 end
5373 end)
5374
5375 local function OnDynamicThumbstickEnabled()
5376 if UserInputService.TouchEnabled then
5377 isDynamicThumbstickEnabled = true
5378 end
5379 end
5380
5381 local function OnDynamicThumbstickDisabled()
5382 isDynamicThumbstickEnabled = false
5383 end
5384
5385 local function OnGameSettingsTouchMovementModeChanged()
5386 if LocalPlayer.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice then
5387 if GameSettings.TouchMovementMode.Name == "DynamicThumbstick" then
5388 OnDynamicThumbstickEnabled()
5389 else
5390 OnDynamicThumbstickDisabled()
5391 end
5392 end
5393 end
5394
5395 local function OnDevTouchMovementModeChanged()
5396 if LocalPlayer.DevTouchMovementMode.Name == "DynamicThumbstick" then
5397 OnDynamicThumbstickEnabled()
5398 else
5399 OnGameSettingsTouchMovementModeChanged()
5400 end
5401 end
5402
5403 if PlayersService.LocalPlayer then
5404 PlayersService.LocalPlayer.Changed:Connect(function(prop)
5405 if prop == "DevTouchMovementMode" then
5406 OnDevTouchMovementModeChanged()
5407 end
5408 end)
5409 OnDevTouchMovementModeChanged()
5410 end
5411
5412 GameSettings.Changed:Connect(function(prop)
5413 if prop == "TouchMovementMode" then
5414 OnGameSettingsTouchMovementModeChanged()
5415 end
5416 end)
5417 OnGameSettingsTouchMovementModeChanged()
5418 GameSettings:SetCameraYInvertVisible()
5419 pcall(function() GameSettings:SetGamepadCameraSensitivityVisible() end)
5420
5421 return this
5422end
5423
5424return CreateCamera
5425
5426
5427local PlayersService = game:GetService('Players')
5428local VRService = game:GetService("VRService")
5429
5430local RootCameraCreator = require(script.Parent)
5431
5432local UP_VECTOR = Vector3.new(0, 1, 0)
5433local XZ_VECTOR = Vector3.new(1, 0, 1)
5434local ZERO_VECTOR2 = Vector2.new(0, 0)
5435
5436local VR_PITCH_FRACTION = 0.25
5437
5438local Vector3_new = Vector3.new
5439local CFrame_new = CFrame.new
5440local math_min = math.min
5441local math_max = math.max
5442local math_atan2 = math.atan2
5443local math_rad = math.rad
5444local math_abs = math.abs
5445
5446local function clamp(low, high, num)
5447 return (num > high and high or num < low and low or num)
5448end
5449
5450local function IsFinite(num)
5451 return num == num and num ~= 1/0 and num ~= -1/0
5452end
5453
5454local function IsFiniteVector3(vec3)
5455 return IsFinite(vec3.x) and IsFinite(vec3.y) and IsFinite(vec3.z)
5456end
5457
5458-- May return NaN or inf or -inf
5459-- This is a way of finding the angle between the two vectors:
5460local function findAngleBetweenXZVectors(vec2, vec1)
5461 return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
5462end
5463
5464local function CreateClassicCamera()
5465 local module = RootCameraCreator()
5466
5467 local tweenAcceleration = math_rad(220)
5468 local tweenSpeed = math_rad(0)
5469 local tweenMaxSpeed = math_rad(250)
5470 local timeBeforeAutoRotate = 2
5471
5472 local lastUpdate = tick()
5473 module.LastUserPanCamera = tick()
5474 function module:Update()
5475 module:ProcessTweens()
5476 local now = tick()
5477 local timeDelta = (now - lastUpdate)
5478
5479 local userPanningTheCamera = (self.UserPanningTheCamera == true)
5480 local camera = workspace.CurrentCamera
5481 local player = PlayersService.LocalPlayer
5482 local humanoid = self:GetHumanoid()
5483 local cameraSubject = camera and camera.CameraSubject
5484 local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
5485 local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')
5486
5487 if lastUpdate == nil or now - lastUpdate > 1 then
5488 module:ResetCameraLook()
5489 self.LastCameraTransform = nil
5490 end
5491
5492 if lastUpdate then
5493 local gamepadRotation = self:UpdateGamepad()
5494
5495 if self:ShouldUseVRRotation() then
5496 self.RotateInput = self.RotateInput + self:GetVRRotationInput()
5497 else
5498 -- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
5499 local delta = math_min(0.1, now - lastUpdate)
5500
5501 if gamepadRotation ~= ZERO_VECTOR2 then
5502 userPanningTheCamera = true
5503 self.RotateInput = self.RotateInput + (gamepadRotation * delta)
5504 end
5505
5506 local angle = 0
5507 if not (isInVehicle or isOnASkateboard) then
5508 angle = angle + (self.TurningLeft and -120 or 0)
5509 angle = angle + (self.TurningRight and 120 or 0)
5510 end
5511
5512 if angle ~= 0 then
5513 self.RotateInput = self.RotateInput + Vector2.new(math_rad(angle * delta), 0)
5514 userPanningTheCamera = true
5515 end
5516 end
5517 end
5518
5519 -- Reset tween speed if user is panning
5520 if userPanningTheCamera then
5521 tweenSpeed = 0
5522 module.LastUserPanCamera = tick()
5523 end
5524
5525 local userRecentlyPannedCamera = now - module.LastUserPanCamera < timeBeforeAutoRotate
5526 local subjectPosition = self:GetSubjectPosition()
5527
5528 if subjectPosition and player and camera then
5529 local zoom = self:GetCameraZoom()
5530 if zoom < 0.5 then
5531 zoom = 0.5
5532 end
5533
5534 if self:GetShiftLock() and not self:IsInFirstPerson() then
5535 -- We need to use the right vector of the camera after rotation, not before
5536 local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
5537 local offset = ((newLookVector * XZ_VECTOR):Cross(UP_VECTOR).unit * 1.75)
5538
5539 if IsFiniteVector3(offset) then
5540 subjectPosition = subjectPosition + offset
5541 end
5542 else
5543 if not userPanningTheCamera and self.LastCameraTransform then
5544 local isInFirstPerson = self:IsInFirstPerson()
5545 if (isInVehicle or isOnASkateboard) and lastUpdate and humanoid and humanoid.Torso then
5546 if isInFirstPerson then
5547 if self.LastSubjectCFrame and (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
5548 local y = -findAngleBetweenXZVectors(self.LastSubjectCFrame.lookVector, cameraSubject.CFrame.lookVector)
5549 if IsFinite(y) then
5550 self.RotateInput = self.RotateInput + Vector2.new(y, 0)
5551 end
5552 tweenSpeed = 0
5553 end
5554 elseif not userRecentlyPannedCamera then
5555 local forwardVector = humanoid.Torso.CFrame.lookVector
5556 if isOnASkateboard then
5557 forwardVector = cameraSubject.CFrame.lookVector
5558 end
5559
5560 tweenSpeed = clamp(0, tweenMaxSpeed, tweenSpeed + tweenAcceleration * timeDelta)
5561
5562 local percent = clamp(0, 1, tweenSpeed * timeDelta)
5563 if self:IsInFirstPerson() then
5564 percent = 1
5565 end
5566
5567 local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
5568 if IsFinite(y) and math_abs(y) > 0.0001 then
5569 self.RotateInput = self.RotateInput + Vector2.new(y * percent, 0)
5570 end
5571 end
5572 end
5573 end
5574 end
5575
5576 local VREnabled = VRService.VREnabled
5577 camera.Focus = VREnabled and self:GetVRFocus(subjectPosition, timeDelta) or CFrame_new(subjectPosition)
5578
5579 local cameraFocusP = camera.Focus.p
5580 if VREnabled and not self:IsInFirstPerson() then
5581 local cameraHeight = self:GetCameraHeight()
5582 local vecToSubject = (subjectPosition - camera.CFrame.p)
5583 local distToSubject = vecToSubject.magnitude
5584
5585 -- Only move the camera if it exceeded a maximum distance to the subject in VR
5586 if distToSubject > zoom or self.RotateInput.x ~= 0 then
5587 local desiredDist = math_min(distToSubject, zoom)
5588 vecToSubject = self:RotateCamera(vecToSubject.unit * XZ_VECTOR, Vector2.new(self.RotateInput.x, 0)) * desiredDist
5589 local newPos = cameraFocusP - vecToSubject
5590 local desiredLookDir = camera.CFrame.lookVector
5591 if self.RotateInput.x ~= 0 then
5592 desiredLookDir = vecToSubject
5593 end
5594 local lookAt = Vector3.new(newPos.x + desiredLookDir.x, newPos.y, newPos.z + desiredLookDir.z)
5595 self.RotateInput = ZERO_VECTOR2
5596
5597 camera.CFrame = CFrame_new(newPos, lookAt) + Vector3_new(0, cameraHeight, 0)
5598 end
5599 else
5600 local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
5601 self.RotateInput = ZERO_VECTOR2
5602 camera.CFrame = CFrame_new(cameraFocusP - (zoom * newLookVector), cameraFocusP)
5603 end
5604
5605 self.LastCameraTransform = camera.CFrame
5606 self.LastCameraFocus = camera.Focus
5607 if (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
5608 self.LastSubjectCFrame = cameraSubject.CFrame
5609 else
5610 self.LastSubjectCFrame = nil
5611 end
5612 end
5613
5614 lastUpdate = now
5615 end
5616
5617 return module
5618end
5619
5620return CreateClassicCamera
5621
5622local PlayersService = game:GetService('Players')
5623local RootCameraCreator = require(script.Parent)
5624
5625local ZERO_VECTOR2 = Vector2.new(0, 0)
5626
5627local CFrame_new = CFrame.new
5628local math_min = math.min
5629
5630local function CreateFixedCamera()
5631 local module = RootCameraCreator()
5632
5633 local lastUpdate = tick()
5634 function module:Update()
5635 local now = tick()
5636
5637 local camera = workspace.CurrentCamera
5638 local player = PlayersService.LocalPlayer
5639 if lastUpdate == nil or now - lastUpdate > 1 then
5640 module:ResetCameraLook()
5641 self.LastCameraTransform = nil
5642 end
5643
5644 if lastUpdate then
5645 -- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
5646 local delta = math_min(0.1, now - lastUpdate)
5647 local gamepadRotation = self:UpdateGamepad()
5648 self.RotateInput = self.RotateInput + (gamepadRotation * delta)
5649 end
5650
5651 local subjectPosition = self:GetSubjectPosition()
5652 if subjectPosition and player and camera then
5653 local zoom = self:GetCameraZoom()
5654 if zoom <= 0 then
5655 zoom = 0.1
5656 end
5657 local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
5658 self.RotateInput = ZERO_VECTOR2
5659
5660 camera.CoordinateFrame = CFrame_new(camera.Focus.p - (zoom * newLookVector), camera.Focus.p)
5661 self.LastCameraTransform = camera.CFrame
5662 end
5663 lastUpdate = now
5664 end
5665
5666 return module
5667end
5668
5669return CreateFixedCamera
5670
5671local PlayersService = game:GetService('Players')
5672local VRService = game:GetService("VRService")
5673
5674local RootCameraCreator = require(script.Parent)
5675
5676local CFrame_new = CFrame.new
5677local Vector2_new = Vector2.new
5678local Vector3_new = Vector3.new
5679local math_min = math.min
5680local math_max = math.max
5681local math_atan2 = math.atan2
5682local math_rad = math.rad
5683local math_abs = math.abs
5684
5685local HUMANOIDSTATE_CLIMBING = Enum.HumanoidStateType.Climbing
5686local ZERO_VECTOR2 = Vector2.new(0, 0)
5687local UP_VECTOR = Vector3.new(0, 1, 0)
5688local XZ_VECTOR = Vector3.new(1, 0, 1)
5689local ZERO_VECTOR3 = Vector3.new(0, 0, 0)
5690local PORTRAIT_OFFSET = Vector3.new(0, -3, 0)
5691
5692local function clamp(low, high, num)
5693 return num > high and high or num < low and low or num
5694end
5695
5696local function IsFinite(num)
5697 return num == num and num ~= 1/0 and num ~= -1/0
5698end
5699
5700local function IsFiniteVector3(vec3)
5701 return IsFinite(vec3.x) and IsFinite(vec3.y) and IsFinite(vec3.z)
5702end
5703
5704-- May return NaN or inf or -inf
5705local function findAngleBetweenXZVectors(vec2, vec1)
5706 -- This is a way of finding the angle between the two vectors:
5707 return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
5708end
5709
5710local function CreateFollowCamera()
5711 local module = RootCameraCreator()
5712
5713 local tweenAcceleration = math_rad(220)
5714 local tweenSpeed = math_rad(0)
5715 local tweenMaxSpeed = math_rad(250)
5716 local timeBeforeAutoRotate = 2
5717
5718 local lastUpdate = tick()
5719 module.LastUserPanCamera = tick()
5720 function module:Update()
5721 module:ProcessTweens()
5722 local now = tick()
5723 local timeDelta = (now - lastUpdate)
5724
5725 local userPanningTheCamera = (self.UserPanningTheCamera == true)
5726 local camera = workspace.CurrentCamera
5727 local player = PlayersService.LocalPlayer
5728 local humanoid = self:GetHumanoid()
5729 local cameraSubject = camera and camera.CameraSubject
5730 local isClimbing = humanoid and humanoid:GetState() == HUMANOIDSTATE_CLIMBING
5731 local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
5732 local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')
5733
5734 if lastUpdate == nil or now - lastUpdate > 1 then
5735 module:ResetCameraLook()
5736 self.LastCameraTransform = nil
5737 end
5738
5739 if lastUpdate then
5740
5741 if self:ShouldUseVRRotation() then
5742 self.RotateInput = self.RotateInput + self:GetVRRotationInput()
5743 else
5744 -- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
5745 local delta = math_min(0.1, now - lastUpdate)
5746 local angle = 0
5747 -- NOTE: Traditional follow camera does not rotate with arrow keys
5748 if not (isInVehicle or isOnASkateboard) then
5749 angle = angle + (self.TurningLeft and -120 or 0)
5750 angle = angle + (self.TurningRight and 120 or 0)
5751 end
5752
5753 local gamepadRotation = self:UpdateGamepad()
5754 if gamepadRotation ~= Vector2.new(0,0) then
5755 userPanningTheCamera = true
5756 self.RotateInput = self.RotateInput + (gamepadRotation * delta)
5757 end
5758
5759 if angle ~= 0 then
5760 userPanningTheCamera = true
5761 self.RotateInput = self.RotateInput + Vector2_new(math_rad(angle * delta), 0)
5762 end
5763 end
5764 end
5765
5766 -- Reset tween speed if user is panning
5767 if userPanningTheCamera then
5768 tweenSpeed = 0
5769 module.LastUserPanCamera = tick()
5770 end
5771
5772 local userRecentlyPannedCamera = now - module.LastUserPanCamera < timeBeforeAutoRotate
5773
5774 local subjectPosition = self:GetSubjectPosition()
5775 if subjectPosition and player and camera then
5776 local zoom = self:GetCameraZoom()
5777 if zoom < 0.5 then
5778 zoom = 0.5
5779 end
5780
5781 if self:GetShiftLock() and not self:IsInFirstPerson() then
5782 local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
5783 local offset = ((newLookVector * XZ_VECTOR):Cross(UP_VECTOR).unit * 1.75)
5784 if IsFiniteVector3(offset) then
5785 subjectPosition = subjectPosition + offset
5786 end
5787 else
5788 if self.LastCameraTransform and not userPanningTheCamera then
5789 local isInFirstPerson = self:IsInFirstPerson()
5790 if (isClimbing or isInVehicle or isOnASkateboard) and lastUpdate and humanoid and humanoid.Torso then
5791 if isInFirstPerson then
5792 if self.LastSubjectCFrame and (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
5793 local y = -findAngleBetweenXZVectors(self.LastSubjectCFrame.lookVector, cameraSubject.CFrame.lookVector)
5794 if IsFinite(y) then
5795 self.RotateInput = self.RotateInput + Vector2.new(y, 0)
5796 end
5797 tweenSpeed = 0
5798 end
5799 elseif not userRecentlyPannedCamera then
5800 local forwardVector = humanoid.Torso.CFrame.lookVector
5801 if isOnASkateboard then
5802 forwardVector = cameraSubject.CFrame.lookVector
5803 end
5804
5805 tweenSpeed = clamp(0, tweenMaxSpeed, tweenSpeed + tweenAcceleration * timeDelta)
5806
5807 local percent = clamp(0, 1, tweenSpeed*timeDelta)
5808 if not isClimbing and self:IsInFirstPerson() then
5809 percent = 1
5810 end
5811 local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
5812 -- Check for NaN
5813 if IsFinite(y) and math_abs(y) > 0.0001 then
5814 self.RotateInput = self.RotateInput + Vector2.new(y * percent, 0)
5815 end
5816 end
5817 elseif not (isInFirstPerson or userRecentlyPannedCamera) and not VRService.VREnabled then
5818 local lastVec = -(self.LastCameraTransform.p - subjectPosition)
5819
5820 local y = findAngleBetweenXZVectors(lastVec, self:GetCameraLook())
5821
5822 -- This cutoff is to decide if the humanoid's angle of movement,
5823 -- relative to the camera's look vector, is enough that
5824 -- we want the camera to be following them. The point is to provide
5825 -- a sizable deadzone to allow more precise forward movements.
5826 local thetaCutoff = 0.4
5827
5828 -- Check for NaNs
5829 if IsFinite(y) and math.abs(y) > 0.0001 and math_abs(y) > thetaCutoff*timeDelta then
5830 self.RotateInput = self.RotateInput + Vector2.new(y, 0)
5831 end
5832 end
5833 end
5834 end
5835 local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
5836 self.RotateInput = ZERO_VECTOR2
5837
5838 if VRService.VREnabled then
5839 camera.Focus = self:GetVRFocus(subjectPosition, timeDelta)
5840 elseif self:IsPortraitMode() then
5841 camera.Focus = CFrame_new(subjectPosition + PORTRAIT_OFFSET)
5842 else
5843 camera.Focus = CFrame_new(subjectPosition)
5844 end
5845 camera.CFrame = CFrame_new(camera.Focus.p - (zoom * newLookVector), camera.Focus.p) + Vector3.new(0, self:GetCameraHeight(), 0)
5846
5847 self.LastCameraTransform = camera.CFrame
5848 self.LastCameraFocus = camera.Focus
5849 if isInVehicle or isOnASkateboard and cameraSubject:IsA('BasePart') then
5850 self.LastSubjectCFrame = cameraSubject.CFrame
5851 else
5852 self.LastSubjectCFrame = nil
5853 end
5854 end
5855
5856 lastUpdate = now
5857 end
5858
5859 return module
5860end
5861
5862return CreateFollowCamera
5863
5864--[[
5865 Orbital Camera 1.0.0
5866 AllYourBlox
5867
5868 Derived from ClassicCamera, adds camera angle constraints, and represents position values in spherical
5869 coordinates (azimuth, elevation, radius), instead of Cartesian coordinates (x, y, z). Azimuth is the
5870 angle of rotation about the Y axis, with the zero-angle reference point corresponding to offsetting
5871 the camera in the +Z direction, from where it will be looking in the -Z direction by default. Elevation
5872 is the angle up from the XZ plane, where zero degrees is on the plane, and +90 degrees is on the +Y
5873 axis. Distance is the camera-to-subject distance, the spherical coordinates radius, specified in studs.
5874--]]
5875
5876-- Do not edit these values, they are not the developer-set limits, they are limits
5877-- to the values the camera system equations can correctly handle
5878local MIN_ALLOWED_ELEVATION_DEG = -80
5879local MAX_ALLOWED_ELEVATION_DEG = 80
5880
5881local externalProperties = {}
5882externalProperties["InitialDistance"] = 25
5883externalProperties["MinDistance"] = 10
5884externalProperties["MaxDistance"] = 100
5885externalProperties["InitialElevation"] = 35
5886externalProperties["MinElevation"] = 35
5887externalProperties["MaxElevation"] = 35
5888externalProperties["ReferenceAzimuth"] = -45 -- Angle around the Y axis where the camera starts. -45 offsets the camera in the -X and +Z directions equally
5889externalProperties["CWAzimuthTravel"] = 90 -- How many degrees the camera is allowed to rotate from the reference position, CW as seen from above
5890externalProperties["CCWAzimuthTravel"] = 90 -- How many degrees the camera is allowed to rotate from the reference position, CCW as seen from above
5891externalProperties["UseAzimuthLimits"] = false -- Full rotation around Y axis available by default
5892
5893local refAzimuthRad
5894local curAzimuthRad
5895local minAzimuthAbsoluteRad
5896local maxAzimuthAbsoluteRad
5897local useAzimuthLimits
5898local curElevationRad
5899local minElevationRad
5900local maxElevationRad
5901local curDistance
5902local minDistance
5903local maxDistance
5904
5905local UNIT_Z = Vector3.new(0,0,1)
5906local TAU = 2 * math.pi
5907
5908local changedSignalConnections = {}
5909
5910-- End of OrbitalCamera additions
5911local PlayersService = game:GetService('Players')
5912local VRService = game:GetService("VRService")
5913
5914local RootCameraCreator = require(script.Parent)
5915
5916local UP_VECTOR = Vector3.new(0, 1, 0)
5917local XZ_VECTOR = Vector3.new(1, 0, 1)
5918local ZERO_VECTOR2 = Vector2.new(0, 0)
5919
5920local VR_PITCH_FRACTION = 0.25
5921
5922local Vector3_new = Vector3.new
5923local CFrame_new = CFrame.new
5924local math_min = math.min
5925local math_max = math.max
5926local math_atan2 = math.atan2
5927local math_rad = math.rad
5928local math_abs = math.abs
5929
5930--[[ Gamepad Support ]]--
5931local THUMBSTICK_DEADZONE = 0.2
5932local r3ButtonDown = false
5933local l3ButtonDown = false
5934local currentZoomSpeed = 1 -- Multiplier, so 1 == no zooming
5935
5936local function clamp(value, minValue, maxValue)
5937 if maxValue < minValue then
5938 maxValue = minValue
5939 end
5940 return math.clamp(value, minValue, maxValue)
5941end
5942
5943local function IsFinite(num)
5944 return num == num and num ~= 1/0 and num ~= -1/0
5945end
5946
5947local function IsFiniteVector3(vec3)
5948 return IsFinite(vec3.x) and IsFinite(vec3.y) and IsFinite(vec3.z)
5949end
5950
5951-- May return NaN or inf or -inf
5952-- This is a way of finding the angle between the two vectors:
5953local function findAngleBetweenXZVectors(vec2, vec1)
5954 return math_atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
5955end
5956
5957local function GetValueObject(name, defaultValue)
5958 local valueObj = script:FindFirstChild(name)
5959 if valueObj then
5960 return valueObj.Value
5961 end
5962 return defaultValue
5963end
5964
5965local function LoadOrCreateNumberValueParameter(name, valueType, updateFunction)
5966 local valueObj = script:FindFirstChild(name)
5967
5968 if valueObj and valueObj:isA(valueType) then
5969 -- Value object exists and is the correct type, use its value
5970 externalProperties[name] = valueObj.Value
5971 elseif externalProperties[name] ~= nil then
5972 -- Create missing (or replace incorrectly-typed) valueObject with default value
5973 valueObj = Instance.new(valueType)
5974 valueObj.Name = name
5975 valueObj.Parent = script
5976 valueObj.Value = externalProperties[name]
5977 else
5978 print("externalProperties table has no entry for ",name)
5979 return
5980 end
5981
5982 if updateFunction then
5983 if changedSignalConnections[name] then
5984 changedSignalConnections[name]:disconnect()
5985 end
5986 changedSignalConnections[name] = valueObj.Changed:connect(function(newValue)
5987 externalProperties[name] = newValue
5988 updateFunction()
5989 end)
5990 end
5991end
5992
5993local function SetAndBoundsCheckAzimuthValues()
5994 minAzimuthAbsoluteRad = math.rad(externalProperties["ReferenceAzimuth"]) - math.abs(math.rad(externalProperties["CWAzimuthTravel"]))
5995 maxAzimuthAbsoluteRad = math.rad(externalProperties["ReferenceAzimuth"]) + math.abs(math.rad(externalProperties["CCWAzimuthTravel"]))
5996 useAzimuthLimits = externalProperties["UseAzimuthLimits"]
5997 if useAzimuthLimits then
5998 curAzimuthRad = math.max(curAzimuthRad, minAzimuthAbsoluteRad)
5999 curAzimuthRad = math.min(curAzimuthRad, maxAzimuthAbsoluteRad)
6000 end
6001end
6002
6003local function SetAndBoundsCheckElevationValues()
6004 -- These degree values are the direct user input values. It is deliberate that they are
6005 -- ranged checked only against the extremes, and not against each other. Any time one
6006 -- is changed, both of the internal values in radians are recalculated. This allows for
6007 -- A developer to change the values in any order and for the end results to be that the
6008 -- internal values adjust to match intent as best as possible.
6009 local minElevationDeg = math.max(externalProperties["MinElevation"], MIN_ALLOWED_ELEVATION_DEG)
6010 local maxElevationDeg = math.min(externalProperties["MaxElevation"], MAX_ALLOWED_ELEVATION_DEG)
6011
6012 -- Set internal values in radians
6013 minElevationRad = math.rad(math.min(minElevationDeg, maxElevationDeg))
6014 maxElevationRad = math.rad(math.max(minElevationDeg, maxElevationDeg))
6015 curElevationRad = math.max(curElevationRad, minElevationRad)
6016 curElevationRad = math.min(curElevationRad, maxElevationRad)
6017end
6018
6019local function SetAndBoundsCheckDistanceValues()
6020 minDistance = externalProperties["MinDistance"]
6021 maxDistance = externalProperties["MaxDistance"]
6022 curDistance = math.max(curDistance, minDistance)
6023 curDistance = math.min(curDistance, maxDistance)
6024end
6025
6026-- This loads from, or lazily creates, NumberValue objects for exposed parameters
6027local function LoadNumberValueParameters()
6028 -- These initial values do not require change listeners since they are read only once
6029 LoadOrCreateNumberValueParameter("InitialElevation", "NumberValue", nil)
6030 LoadOrCreateNumberValueParameter("InitialDistance", "NumberValue", nil)
6031
6032 -- Note: ReferenceAzimuth is also used as an initial value, but needs a change listener because it is used in the calculation of the limits
6033 LoadOrCreateNumberValueParameter("ReferenceAzimuth", "NumberValue", SetAndBoundsCheckAzimuthValues)
6034 LoadOrCreateNumberValueParameter("CWAzimuthTravel", "NumberValue", SetAndBoundsCheckAzimuthValues)
6035 LoadOrCreateNumberValueParameter("CCWAzimuthTravel", "NumberValue", SetAndBoundsCheckAzimuthValues)
6036 LoadOrCreateNumberValueParameter("MinElevation", "NumberValue", SetAndBoundsCheckElevationValues)
6037 LoadOrCreateNumberValueParameter("MaxElevation", "NumberValue", SetAndBoundsCheckElevationValues)
6038 LoadOrCreateNumberValueParameter("MinDistance", "NumberValue", SetAndBoundsCheckDistanceValues)
6039 LoadOrCreateNumberValueParameter("MaxDistance", "NumberValue", SetAndBoundsCheckDistanceValues)
6040 LoadOrCreateNumberValueParameter("UseAzimuthLimits", "BoolValue", SetAndBoundsCheckAzimuthValues)
6041
6042 -- Internal values set (in radians, from degrees), plus sanitization
6043 curAzimuthRad = math.rad(externalProperties["ReferenceAzimuth"])
6044 curElevationRad = math.rad(externalProperties["InitialElevation"])
6045 curDistance = externalProperties["InitialDistance"]
6046
6047 SetAndBoundsCheckAzimuthValues()
6048 SetAndBoundsCheckElevationValues()
6049 SetAndBoundsCheckDistanceValues()
6050end
6051
6052local function CreateOrbitalCamera()
6053 local module = RootCameraCreator()
6054
6055 LoadNumberValueParameters()
6056
6057 module.DefaultZoom = curDistance
6058
6059 local tweenAcceleration = math_rad(220)
6060 local tweenSpeed = math_rad(0)
6061 local tweenMaxSpeed = math_rad(250)
6062 local timeBeforeAutoRotate = 2
6063
6064 local lastUpdate = tick()
6065 module.LastUserPanCamera = tick()
6066
6067 function module:Update()
6068 module:ProcessTweens()
6069 local now = tick()
6070 local timeDelta = (now - lastUpdate)
6071 local userPanningTheCamera = (self.UserPanningTheCamera == true)
6072 local camera = workspace.CurrentCamera
6073 local player = PlayersService.LocalPlayer
6074 local humanoid = self:GetHumanoid()
6075 local cameraSubject = camera and camera.CameraSubject
6076 local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
6077 local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')
6078
6079 if lastUpdate == nil or now - lastUpdate > 1 then
6080 module:ResetCameraLook()
6081 self.LastCameraTransform = nil
6082 end
6083
6084 if lastUpdate then
6085 local gamepadRotation = self:UpdateGamepad()
6086
6087 if self:ShouldUseVRRotation() then
6088 self.RotateInput = self.RotateInput + self:GetVRRotationInput()
6089 else
6090 -- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
6091 local delta = math_min(0.1, now - lastUpdate)
6092
6093 if gamepadRotation ~= ZERO_VECTOR2 then
6094 userPanningTheCamera = true
6095 self.RotateInput = self.RotateInput + (gamepadRotation * delta)
6096 end
6097
6098 local angle = 0
6099 if not (isInVehicle or isOnASkateboard) then
6100 angle = angle + (self.TurningLeft and -120 or 0)
6101 angle = angle + (self.TurningRight and 120 or 0)
6102 end
6103
6104 if angle ~= 0 then
6105 self.RotateInput = self.RotateInput + Vector2.new(math_rad(angle * delta), 0)
6106 userPanningTheCamera = true
6107 end
6108 end
6109 end
6110
6111 -- Reset tween speed if user is panning
6112 if userPanningTheCamera then
6113 tweenSpeed = 0
6114 module.LastUserPanCamera = tick()
6115 end
6116
6117 local userRecentlyPannedCamera = now - module.LastUserPanCamera < timeBeforeAutoRotate
6118 local subjectPosition = self:GetSubjectPosition()
6119
6120 if subjectPosition and player and camera then
6121
6122
6123 local zoom = self:ZoomCamera(curDistance * currentZoomSpeed)
6124
6125
6126 if not userPanningTheCamera and self.LastCameraTransform then
6127 local isInFirstPerson = self:IsInFirstPerson()
6128 if (isInVehicle or isOnASkateboard) and lastUpdate and humanoid and humanoid.Torso then
6129 if isInFirstPerson then
6130 if self.LastSubjectCFrame and (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
6131 local y = -findAngleBetweenXZVectors(self.LastSubjectCFrame.lookVector, cameraSubject.CFrame.lookVector)
6132 if IsFinite(y) then
6133 self.RotateInput = self.RotateInput + Vector2.new(y, 0)
6134 end
6135 tweenSpeed = 0
6136 end
6137 elseif not userRecentlyPannedCamera then
6138 local forwardVector = humanoid.Torso.CFrame.lookVector
6139 if isOnASkateboard then
6140 forwardVector = cameraSubject.CFrame.lookVector
6141 end
6142
6143 tweenSpeed = clamp(0, tweenMaxSpeed, tweenSpeed + tweenAcceleration * timeDelta)
6144
6145 local percent = clamp(0, 1, tweenSpeed * timeDelta)
6146 if self:IsInFirstPerson() then
6147 percent = 1
6148 end
6149
6150 local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
6151 if IsFinite(y) and math_abs(y) > 0.0001 then
6152 self.RotateInput = self.RotateInput + Vector2.new(y * percent, 0)
6153 end
6154 end
6155 end
6156 end
6157
6158 local VREnabled = VRService.VREnabled
6159 camera.Focus = VREnabled and self:GetVRFocus(subjectPosition, timeDelta) or CFrame_new(subjectPosition)
6160
6161 local cameraFocusP = camera.Focus.p
6162 if VREnabled and not self:IsInFirstPerson() then
6163 local cameraHeight = self:GetCameraHeight()
6164 local vecToSubject = (subjectPosition - camera.CFrame.p)
6165 local distToSubject = vecToSubject.magnitude
6166
6167 -- Only move the camera if it exceeded a maximum distance to the subject in VR
6168 if distToSubject > zoom or self.RotateInput.x ~= 0 then
6169 local desiredDist = math_min(distToSubject, zoom)
6170 vecToSubject = self:RotateCamera(vecToSubject.unit * XZ_VECTOR, Vector2.new(self.RotateInput.x, 0)) * desiredDist
6171 local newPos = cameraFocusP - vecToSubject
6172 local desiredLookDir = camera.CFrame.lookVector
6173 if self.RotateInput.x ~= 0 then
6174 desiredLookDir = vecToSubject
6175 end
6176 local lookAt = Vector3.new(newPos.x + desiredLookDir.x, newPos.y, newPos.z + desiredLookDir.z)
6177 self.RotateInput = ZERO_VECTOR2
6178
6179 camera.CFrame = CFrame_new(newPos, lookAt) + Vector3_new(0, cameraHeight, 0)
6180 end
6181 else
6182 -- self.RotateInput is a Vector2 of mouse movement deltas since last update
6183 curAzimuthRad = curAzimuthRad - self.RotateInput.x
6184
6185 if useAzimuthLimits then
6186 curAzimuthRad = clamp(curAzimuthRad, minAzimuthAbsoluteRad, maxAzimuthAbsoluteRad)
6187 else
6188 curAzimuthRad = (curAzimuthRad ~= 0) and (math.sign(curAzimuthRad) * (math.abs(curAzimuthRad) % TAU)) or 0
6189 end
6190
6191 curDistance = clamp(zoom, minDistance, maxDistance)
6192
6193 curElevationRad = clamp(curElevationRad + self.RotateInput.y, minElevationRad,maxElevationRad)
6194
6195 local cameraPosVector = curDistance * ( CFrame.fromEulerAnglesYXZ( -curElevationRad, curAzimuthRad, 0 ) * UNIT_Z )
6196 local camPos = subjectPosition + cameraPosVector
6197
6198 camera.CFrame = CFrame.new(camPos, subjectPosition)
6199
6200 self.RotateInput = ZERO_VECTOR2
6201 end
6202
6203 self.LastCameraTransform = camera.CFrame
6204 self.LastCameraFocus = camera.Focus
6205 if (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
6206 self.LastSubjectCFrame = cameraSubject.CFrame
6207 else
6208 self.LastSubjectCFrame = nil
6209 end
6210 end
6211
6212 lastUpdate = now
6213 end
6214
6215 function module:GetCameraZoom()
6216 return curDistance
6217 end
6218
6219 function module:ZoomCamera(desiredZoom)
6220 local player = PlayersService.LocalPlayer
6221 if player then
6222 curDistance = clamp(desiredZoom, minDistance, maxDistance)
6223 end
6224
6225 isFirstPerson = self:GetCameraZoom() < 2
6226
6227 --ShiftLockController:SetIsInFirstPerson(isFirstPerson)
6228 -- set mouse behavior
6229 self:UpdateMouseBehavior()
6230 return self:GetCameraZoom()
6231 end
6232
6233 function module:ZoomCameraBy(zoomScale)
6234 local newDist = curDistance
6235 -- Can break into more steps to get more accurate integration
6236 if UserSettings():IsUserFeatureEnabled("UserBetterInertialScrolling") then
6237 newDist = self:rk4Integrator(curDistance, zoomScale, 1/10)
6238 else
6239 newDist = self:rk4Integrator(curDistance, zoomScale, 1)
6240 end
6241 self:ZoomCamera(newDist)
6242 return self:GetCameraZoom()
6243 end
6244
6245 function module:ZoomCameraFixedBy(zoomIncrement)
6246 return self:ZoomCamera(self:GetCameraZoom() + zoomIncrement)
6247 end
6248
6249 function module:SetInitialOrientation(humanoid)
6250 local newDesiredLook = (humanoid.Torso.CFrame.lookVector - Vector3.new(0,0.23,0)).unit
6251 local horizontalShift = findAngleBetweenXZVectors(newDesiredLook, self:GetCameraLook())
6252 local vertShift = math.asin(self:GetCameraLook().y) - math.asin(newDesiredLook.y)
6253 if not IsFinite(horizontalShift) then
6254 horizontalShift = 0
6255 end
6256 if not IsFinite(vertShift) then
6257 vertShift = 0
6258 end
6259 self.RotateInput = Vector2.new(horizontalShift, vertShift)
6260 end
6261
6262 function module.getGamepadPan(name, state, input)
6263 if input.UserInputType == module.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
6264 if r3ButtonDown or l3ButtonDown then
6265 -- R3 or L3 Thumbstick is depressed, right stick controls dolly in/out
6266 if (input.Position.Y > THUMBSTICK_DEADZONE) then
6267 currentZoomSpeed = 0.96
6268 elseif (input.Position.Y < -THUMBSTICK_DEADZONE) then
6269 currentZoomSpeed = 1.04
6270 else
6271 currentZoomSpeed = 1.00
6272 end
6273 else
6274 if state == Enum.UserInputState.Cancel then
6275 module.GamepadPanningCamera = ZERO_VECTOR2
6276 return
6277 end
6278
6279 local inputVector = Vector2.new(input.Position.X, -input.Position.Y)
6280 if inputVector.magnitude > THUMBSTICK_DEADZONE then
6281 module.GamepadPanningCamera = Vector2.new(input.Position.X, -input.Position.Y)
6282 else
6283 module.GamepadPanningCamera = ZERO_VECTOR2
6284 end
6285 end
6286 end
6287 end
6288
6289 function module.doGamepadZoom(name, state, input)
6290 if input.UserInputType == module.activeGamepad and (input.KeyCode == Enum.KeyCode.ButtonR3 or input.KeyCode == Enum.KeyCode.ButtonL3) then
6291 if (state == Enum.UserInputState.Begin) then
6292 r3ButtonDown = input.KeyCode == Enum.KeyCode.ButtonR3
6293 l3ButtonDown = input.KeyCode == Enum.KeyCode.ButtonL3
6294 elseif (state == Enum.UserInputState.End) then
6295 if (input.KeyCode == Enum.KeyCode.ButtonR3) then
6296 r3ButtonDown = false
6297 elseif (input.KeyCode == Enum.KeyCode.ButtonL3) then
6298 l3ButtonDown = false
6299 end
6300 if (not r3ButtonDown) and (not l3ButtonDown) then
6301 currentZoomSpeed = 1.00
6302 end
6303 end
6304 end
6305 end
6306
6307 function module:BindGamepadInputActions()
6308 local ContextActionService = game:GetService('ContextActionService')
6309 ContextActionService:BindAction("OrbitalCamGamepadPan", module.getGamepadPan, false, Enum.KeyCode.Thumbstick2)
6310 ContextActionService:BindAction("OrbitalCamGamepadZoom", module.doGamepadZoom, false, Enum.KeyCode.ButtonR3)
6311 ContextActionService:BindAction("OrbitalCamGamepadZoomAlt", module.doGamepadZoom, false, Enum.KeyCode.ButtonL3)
6312 end
6313
6314 return module
6315end
6316
6317return CreateOrbitalCamera
6318
6319local RootCameraCreator = require(script.Parent)
6320
6321local function CreateScriptableCamera()
6322 local module = RootCameraCreator()
6323
6324 function module:Update()
6325 end
6326
6327 return module
6328end
6329
6330return CreateScriptableCamera
6331
6332local PlayersService = game:GetService('Players')
6333local RootCameraCreator = require(script.Parent)
6334
6335local ZERO_VECTOR2 = Vector2.new(0, 0)
6336
6337local CFrame_new = CFrame.new
6338local math_min = math.min
6339
6340local function CreateTrackCamera()
6341 local module = RootCameraCreator()
6342
6343 local lastUpdate = tick()
6344 function module:Update()
6345 local now = tick()
6346
6347 local userPanningTheCamera = (self.UserPanningTheCamera == true)
6348 local camera = workspace.CurrentCamera
6349 local player = PlayersService.LocalPlayer
6350
6351 if lastUpdate == nil or now - lastUpdate > 1 then
6352 module:ResetCameraLook()
6353 self.LastCameraTransform = nil
6354 end
6355
6356 if lastUpdate then
6357 -- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
6358 local delta = math_min(0.1, now - lastUpdate)
6359 local gamepadRotation = self:UpdateGamepad()
6360 if gamepadRotation ~= ZERO_VECTOR2 then
6361 userPanningTheCamera = true
6362 self.RotateInput = self.RotateInput + (gamepadRotation * delta)
6363 end
6364 end
6365
6366 local subjectPosition = self:GetSubjectPosition()
6367 if subjectPosition and player and camera then
6368 local zoom = self:GetCameraZoom()
6369 if zoom <= 0 then
6370 zoom = 0.1
6371 end
6372 local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
6373 self.RotateInput = ZERO_VECTOR2
6374
6375 camera.Focus = CFrame_new(subjectPosition)
6376 camera.CFrame = CFrame_new(subjectPosition - (zoom * newLookVector), subjectPosition)
6377 self.LastCameraTransform = camera.CoordinateFrame
6378 end
6379 lastUpdate = now
6380 end
6381
6382 return module
6383end
6384
6385return CreateTrackCamera
6386
6387local PlayersService = game:GetService('Players')
6388local RootCameraCreator = require(script.Parent)
6389
6390local ZERO_VECTOR2 = Vector2.new(0, 0)
6391
6392local CFrame_new = CFrame.new
6393
6394local function CreateWatchCamera()
6395 local module = RootCameraCreator()
6396 module.PanEnabled = false
6397
6398 local lastUpdate = tick()
6399 function module:Update()
6400 local now = tick()
6401
6402 local camera = workspace.CurrentCamera
6403 local player = PlayersService.LocalPlayer
6404
6405 if lastUpdate == nil or now - lastUpdate > 1 then
6406 module:ResetCameraLook()
6407 self.LastCameraTransform = nil
6408 self.LastZoom = nil
6409 end
6410
6411
6412 local subjectPosition = self:GetSubjectPosition()
6413 if subjectPosition and player and camera then
6414 local cameraLook = nil
6415
6416 if self.LastCameraTransform then
6417 local humanoid = self:GetHumanoid()
6418 if humanoid and humanoid.Torso then
6419 -- TODO: let the paging buttons move the camera but not the mouse/touch
6420 -- currently neither do
6421 local diffVector = subjectPosition - self.LastCameraTransform.p
6422 cameraLook = diffVector.unit
6423
6424 if self.LastZoom and self.LastZoom == self:GetCameraZoom() then
6425 -- Don't clobber the zoom if they zoomed the camera
6426 local zoom = diffVector.magnitude
6427 self:ZoomCamera(zoom)
6428 end
6429 end
6430 end
6431
6432 local zoom = self:GetCameraZoom()
6433 if zoom <= 0 then
6434 zoom = 0.1
6435 end
6436
6437 local newLookVector = self:RotateVector(cameraLook or self:GetCameraLook(), self.RotateInput)
6438 self.RotateInput = ZERO_VECTOR2
6439 local newFocus = CFrame_new(subjectPosition)
6440 local newCamCFrame = CFrame_new(newFocus.p - (zoom * newLookVector), subjectPosition)
6441
6442 camera.Focus = newFocus
6443 camera.CFrame = newCamCFrame
6444 self.LastCameraTransform = newCamCFrame
6445 self.LastZoom = zoom
6446 end
6447 lastUpdate = now
6448 end
6449
6450 return module
6451end
6452
6453return CreateWatchCamera
6454
6455--[[
6456 // FileName: ShiftLockController
6457 // Written by: jmargh
6458 // Version 1.2
6459 // Description: Manages the state of shift lock mode
6460
6461 // Required by:
6462 RootCamera
6463
6464 // Note: ContextActionService sinks keys, so until we allow binding to ContextActionService without sinking
6465 // keys, this module will use UserInputService.
6466--]]
6467
6468local Players = game:GetService('Players')
6469local UserInputService = game:GetService('UserInputService')
6470-- Settings and GameSettings are read only
6471local Settings = UserSettings() -- ignore warning
6472local GameSettings = Settings.GameSettings
6473
6474local ShiftLockController = {}
6475
6476--[[ Script Variables ]]--
6477while not Players.LocalPlayer do
6478 Players.PlayerAdded:wait()
6479end
6480
6481local LocalPlayer = Players.LocalPlayer
6482local Mouse = LocalPlayer:GetMouse()
6483local PlayerGui = LocalPlayer:WaitForChild('PlayerGui')
6484local ScreenGui = nil
6485local ShiftLockIcon = nil
6486local InputCn = nil
6487local IsShiftLockMode = false
6488local IsShiftLocked = false
6489local IsActionBound = false
6490local IsInFirstPerson = false
6491
6492-- Toggle Event
6493ShiftLockController.OnShiftLockToggled = Instance.new('BindableEvent')
6494
6495-- wrapping long conditional in function
6496local function isShiftLockMode()
6497 return LocalPlayer.DevEnableMouseLock and GameSettings.ControlMode == Enum.ControlMode.MouseLockSwitch and
6498 LocalPlayer.DevComputerMovementMode ~= Enum.DevComputerMovementMode.ClickToMove and
6499 GameSettings.ComputerMovementMode ~= Enum.ComputerMovementMode.ClickToMove and
6500 LocalPlayer.DevComputerMovementMode ~= Enum.DevComputerMovementMode.Scriptable
6501end
6502
6503if not UserInputService.TouchEnabled then -- TODO: Remove when safe on mobile
6504 IsShiftLockMode = isShiftLockMode()
6505end
6506
6507--[[ Constants ]]--
6508local SHIFT_LOCK_OFF = 'rbxasset://textures/ui/mouseLock_off.png'
6509local SHIFT_LOCK_ON = 'rbxasset://textures/ui/mouseLock_on.png'
6510local SHIFT_LOCK_CURSOR = 'rbxasset://textures/MouseLockedCursor.png'
6511
6512--[[ Local Functions ]]--
6513local function onShiftLockToggled()
6514 IsShiftLocked = not IsShiftLocked
6515 if IsShiftLocked then
6516 ShiftLockIcon.Image = SHIFT_LOCK_ON
6517 Mouse.Icon = SHIFT_LOCK_CURSOR
6518 else
6519 ShiftLockIcon.Image = SHIFT_LOCK_OFF
6520 Mouse.Icon = ""
6521 end
6522 ShiftLockController.OnShiftLockToggled:Fire()
6523end
6524
6525local function initialize()
6526 if ScreenGui then
6527 ScreenGui:Destroy()
6528 ScreenGui = nil
6529 end
6530 ScreenGui = Instance.new('ScreenGui')
6531 ScreenGui.Name = "ControlGui"
6532
6533 local frame = Instance.new('Frame')
6534 frame.Name = "BottomLeftControl"
6535 frame.Size = UDim2.new(0, 130, 0, 46)
6536 frame.Position = UDim2.new(0, 0, 1, -46)
6537 frame.BackgroundTransparency = 1
6538 frame.Parent = ScreenGui
6539
6540 ShiftLockIcon = Instance.new('ImageButton')
6541 ShiftLockIcon.Name = "MouseLockLabel"
6542 ShiftLockIcon.Size = UDim2.new(0, 31, 0, 31)
6543 ShiftLockIcon.Position = UDim2.new(0, 12, 0, 2)
6544 ShiftLockIcon.BackgroundTransparency = 1
6545 ShiftLockIcon.Image = IsShiftLocked and SHIFT_LOCK_ON or SHIFT_LOCK_OFF
6546 ShiftLockIcon.Visible = true
6547 ShiftLockIcon.Parent = frame
6548
6549 ShiftLockIcon.MouseButton1Click:connect(onShiftLockToggled)
6550
6551 ScreenGui.Parent = IsShiftLockMode and PlayerGui or nil
6552end
6553
6554--[[ Public API ]]--
6555function ShiftLockController:IsShiftLocked()
6556 return IsShiftLockMode and IsShiftLocked
6557end
6558
6559function ShiftLockController:SetIsInFirstPerson(isInFirstPerson)
6560 IsInFirstPerson = isInFirstPerson
6561end
6562
6563--[[ Input/Settings Changed Events ]]--
6564local mouseLockSwitchFunc = function(actionName, inputState, inputObject)
6565 if IsShiftLockMode then
6566 onShiftLockToggled()
6567 end
6568end
6569
6570local function disableShiftLock()
6571 if ScreenGui then ScreenGui.Parent = nil end
6572 IsShiftLockMode = false
6573 Mouse.Icon = ""
6574 if InputCn then
6575 InputCn:disconnect()
6576 InputCn = nil
6577 end
6578 IsActionBound = false
6579 ShiftLockController.OnShiftLockToggled:Fire()
6580end
6581
6582-- TODO: Remove when we figure out ContextActionService without sinking keys
6583local function onShiftInputBegan(inputObject, isProcessed)
6584 if isProcessed then return end
6585 if inputObject.UserInputType == Enum.UserInputType.Keyboard and
6586 (inputObject.KeyCode == Enum.KeyCode.LeftShift or inputObject.KeyCode == Enum.KeyCode.RightShift) then
6587 --
6588 mouseLockSwitchFunc()
6589 end
6590end
6591
6592local function enableShiftLock()
6593 IsShiftLockMode = isShiftLockMode()
6594 if IsShiftLockMode then
6595 if ScreenGui then
6596 ScreenGui.Parent = PlayerGui
6597 end
6598 if IsShiftLocked then
6599 Mouse.Icon = SHIFT_LOCK_CURSOR
6600 ShiftLockController.OnShiftLockToggled:Fire()
6601 end
6602 if not IsActionBound then
6603 InputCn = UserInputService.InputBegan:connect(onShiftInputBegan)
6604 IsActionBound = true
6605 end
6606 end
6607end
6608
6609GameSettings.Changed:connect(function(property)
6610 if property == 'ControlMode' then
6611 if GameSettings.ControlMode == Enum.ControlMode.MouseLockSwitch then
6612 enableShiftLock()
6613 else
6614 disableShiftLock()
6615 end
6616 elseif property == 'ComputerMovementMode' then
6617 if GameSettings.ComputerMovementMode == Enum.ComputerMovementMode.ClickToMove then
6618 disableShiftLock()
6619 else
6620 enableShiftLock()
6621 end
6622 end
6623end)
6624
6625LocalPlayer.Changed:connect(function(property)
6626 if property == 'DevEnableMouseLock' then
6627 if LocalPlayer.DevEnableMouseLock then
6628 enableShiftLock()
6629 else
6630 disableShiftLock()
6631 end
6632 elseif property == 'DevComputerMovementMode' then
6633 if LocalPlayer.DevComputerMovementMode == Enum.DevComputerMovementMode.ClickToMove or
6634 LocalPlayer.DevComputerMovementMode == Enum.DevComputerMovementMode.Scriptable then
6635 --
6636 disableShiftLock()
6637 else
6638 enableShiftLock()
6639 end
6640 end
6641end)
6642
6643LocalPlayer.CharacterAdded:connect(function(character)
6644 -- we need to recreate guis on character load
6645 if not UserInputService.TouchEnabled then
6646 initialize()
6647 end
6648end)
6649
6650--[[ Initialization ]]--
6651 -- TODO: Remove when safe! ContextActionService crashes touch clients with tupele is 2 or more
6652if not UserInputService.TouchEnabled then
6653 initialize()
6654 if isShiftLockMode() then
6655 InputCn = UserInputService.InputBegan:connect(onShiftInputBegan)
6656 IsActionBound = true
6657 end
6658end
6659
6660return ShiftLockController
6661
6662-- SolarCrane
6663
6664local MAX_TWEEN_RATE = 2.8 -- per second
6665
6666local function clamp(low, high, num)
6667 return (num > high and high or num < low and low or num)
6668end
6669
6670local math_floor = math.floor
6671
6672local function Round(num, places)
6673 local decimalPivot = 10^places
6674 return math_floor(num * decimalPivot + 0.5) / decimalPivot
6675end
6676
6677local function CreateTransparencyController()
6678 local module = {}
6679
6680
6681 local LastUpdate = tick()
6682 local TransparencyDirty = false
6683 local Enabled = false
6684 local LastTransparency = nil
6685
6686 local DescendantAddedConn, DescendantRemovingConn = nil, nil
6687 local ToolDescendantAddedConns = {}
6688 local ToolDescendantRemovingConns = {}
6689 local CachedParts = {}
6690
6691 local function HasToolAncestor(object)
6692 if object.Parent == nil then return false end
6693 return object.Parent:IsA('Tool') or HasToolAncestor(object.Parent)
6694 end
6695
6696 local function IsValidPartToModify(part)
6697 if part:IsA('BasePart') or part:IsA('Decal') then
6698 return not HasToolAncestor(part)
6699 end
6700 return false
6701 end
6702
6703 local function CachePartsRecursive(object)
6704 if object then
6705 if IsValidPartToModify(object) then
6706 CachedParts[object] = true
6707 TransparencyDirty = true
6708 end
6709 for _, child in pairs(object:GetChildren()) do
6710 CachePartsRecursive(child)
6711 end
6712 end
6713 end
6714
6715 local function TeardownTransparency()
6716 for child, _ in pairs(CachedParts) do
6717 child.LocalTransparencyModifier = 0
6718 end
6719 CachedParts = {}
6720 TransparencyDirty = true
6721 LastTransparency = nil
6722
6723 if DescendantAddedConn then
6724 DescendantAddedConn:disconnect()
6725 DescendantAddedConn = nil
6726 end
6727 if DescendantRemovingConn then
6728 DescendantRemovingConn:disconnect()
6729 DescendantRemovingConn = nil
6730 end
6731 for object, conn in pairs(ToolDescendantAddedConns) do
6732 conn:disconnect()
6733 ToolDescendantAddedConns[object] = nil
6734 end
6735 for object, conn in pairs(ToolDescendantRemovingConns) do
6736 conn:disconnect()
6737 ToolDescendantRemovingConns[object] = nil
6738 end
6739 end
6740
6741 local function SetupTransparency(character)
6742 TeardownTransparency()
6743
6744 if DescendantAddedConn then DescendantAddedConn:disconnect() end
6745 DescendantAddedConn = character.DescendantAdded:connect(function(object)
6746 -- This is a part we want to invisify
6747 if IsValidPartToModify(object) then
6748 CachedParts[object] = true
6749 TransparencyDirty = true
6750 -- There is now a tool under the character
6751 elseif object:IsA('Tool') then
6752 if ToolDescendantAddedConns[object] then ToolDescendantAddedConns[object]:disconnect() end
6753 ToolDescendantAddedConns[object] = object.DescendantAdded:connect(function(toolChild)
6754 CachedParts[toolChild] = nil
6755 if toolChild:IsA('BasePart') or toolChild:IsA('Decal') then
6756 -- Reset the transparency
6757 toolChild.LocalTransparencyModifier = 0
6758 end
6759 end)
6760 if ToolDescendantRemovingConns[object] then ToolDescendantRemovingConns[object]:disconnect() end
6761 ToolDescendantRemovingConns[object] = object.DescendantRemoving:connect(function(formerToolChild)
6762 wait() -- wait for new parent
6763 if character and formerToolChild and formerToolChild:IsDescendantOf(character) then
6764 if IsValidPartToModify(formerToolChild) then
6765 CachedParts[formerToolChild] = true
6766 TransparencyDirty = true
6767 end
6768 end
6769 end)
6770 end
6771 end)
6772 if DescendantRemovingConn then DescendantRemovingConn:disconnect() end
6773 DescendantRemovingConn = character.DescendantRemoving:connect(function(object)
6774 if CachedParts[object] then
6775 CachedParts[object] = nil
6776 -- Reset the transparency
6777 object.LocalTransparencyModifier = 0
6778 end
6779 end)
6780 CachePartsRecursive(character)
6781 end
6782
6783
6784 function module:SetEnabled(newState)
6785 if Enabled ~= newState then
6786 Enabled = newState
6787 self:Update()
6788 end
6789 end
6790
6791 function module:SetSubject(subject)
6792 local character = nil
6793 if subject and subject:IsA("Humanoid") then
6794 character = subject.Parent
6795 end
6796 if subject and subject:IsA("VehicleSeat") and subject.Occupant then
6797 character = subject.Occupant.Parent
6798 end
6799 if character then
6800 SetupTransparency(character)
6801 else
6802 TeardownTransparency()
6803 end
6804 end
6805
6806 function module:Update()
6807 local instant = false
6808 local now = tick()
6809 local currentCamera = workspace.CurrentCamera
6810
6811 if currentCamera then
6812 local transparency = 0
6813 if not Enabled then
6814 instant = true
6815 else
6816 local distance = (currentCamera.Focus.p - currentCamera.CoordinateFrame.p).magnitude
6817 transparency = (7 - distance) / 5
6818 if transparency < .5 then
6819 transparency = 0
6820 end
6821
6822 if LastTransparency then
6823 local deltaTransparency = transparency - LastTransparency
6824 -- Don't tween transparency if it is instant or your character was fully invisible last frame
6825 if not instant and transparency < 1 and LastTransparency < 0.95 then
6826 local maxDelta = MAX_TWEEN_RATE * (now - LastUpdate)
6827 deltaTransparency = clamp(-maxDelta, maxDelta, deltaTransparency)
6828 end
6829 transparency = LastTransparency + deltaTransparency
6830 else
6831 TransparencyDirty = true
6832 end
6833
6834 transparency = clamp(0, 1, Round(transparency, 2))
6835 end
6836
6837 if TransparencyDirty or LastTransparency ~= transparency then
6838 for child, _ in pairs(CachedParts) do
6839 if child.Name ~= 'LeftHand' and child.Name ~= 'LeftLowerArm' and child.Name ~= 'LeftUpperArm'
6840 and child.Name ~= 'RightHand' and child.Name ~= 'RightLowerArm' and child.Name ~= 'RightUpperArm'
6841 and child.Name ~= 'Right Arm' and child.Name ~= 'Left Arm' then
6842 child.LocalTransparencyModifier = transparency
6843 else
6844 child.LocalTransparencyModifier = 0
6845 end
6846 end
6847 TransparencyDirty = false
6848 LastTransparency = transparency
6849 end
6850 end
6851 LastUpdate = now
6852 end
6853
6854 return module
6855end
6856
6857return CreateTransparencyController