local function I(I)if UserInputService.TouchEnabled then for I,O in ipairs(LocalPlayer.Character:GetChildren())do if O:IsA("\084\111\111\108")then IsAToolEquipped=true end end I.ChildAdded:Connect(function(I)if I:IsA("\084\111\111\108")then IsAToolEquipped=true end end)I.ChildRemoved:Connect(function(I)if I:IsA("\084\111\111\108")then IsAToolEquipped=false end end)if PlayerGui then local I=PlayerGui:FindFirstChild("\084\111\117\099\104\071\117\105")if I and I:WaitForChild("\071\101\115\116\117\114\101\065\114\101\097",.5)then GestureArea=I.GestureArea GestureAreaManagedByControlScript=true else GestureAreaManagedByControlScript=false local I=Instance.new("\083\099\114\101\101\110\071\117\105")I.Name="\071\101\115\116\117\114\101\065\114\101\097"I.Parent=PlayerGui GestureArea=Instance.new("\070\114\097\109\101")GestureArea.BackgroundTransparency=1.0 GestureArea.Visible=true GestureArea.BackgroundColor3=Color3.fromRGB(0,0,0)layoutGestureArea(PortraitMode)GestureArea.Parent=I end end end end if LocalPlayer then if LocalPlayer.Character~=nil then I(LocalPlayer.Character)end LocalPlayer.CharacterAdded:connect(function(O)I(O)end)end local function O(I,O)if I.X<O.AbsolutePosition.X+O.AbsoluteSize.X and(I.X>O.AbsolutePosition.X and(I.Y<O.AbsolutePosition.Y+O.AbsoluteSize.Y and I.Y>O.AbsolutePosition.Y))then return true end return false end local function o(I)return I:GetRenderCFrame()end local function v()local I={}local v=R15_HEAD_OFFSET function I.GetActivateValue(I)return.7 end function I.IsPortraitMode(I)return PortraitMode end function I.GetRotateAmountValue(O,I)I=I or StarterGui:GetCore("\086\082\082\111\116\097\116\105\111\110\073\110\116\101\110\115\105\116\121")if I then if I=="\076\111\119"then return VR_LOW_INTENSITY_ROTATION elseif I=="\072\105\103\104"then return VR_HIGH_INTENSITY_ROTATION end end return ZERO_VECTOR2 end function I.GetRepeatDelayValue(O,I)I=I or StarterGui:GetCore("\086\082\082\111\116\097\116\105\111\110\073\110\116\101\110\115\105\116\121")if I then if I=="\076\111\119"then return VR_LOW_INTENSITY_REPEAT elseif I=="\072\105\103\104"then return VR_HIGH_INTENSITY_REPEAT end end return 0 end I.ShiftLock=false I.Enabled=false local s=false local Y=false local c=false I.RotateInput=ZERO_VECTOR2 I.DefaultZoom=LANDSCAPE_DEFAULT_ZOOM I.activeGamepad=nil local V={}I.lastSubject=nil I.lastSubjectPosition=Vector3.new(0,5,0)local e=0 local b={}local U=false local a=nil local function C()if a and a:IsDescendantOf(game)then return a else local I=PlayerGui:FindFirstChild("\084\111\117\099\104\071\117\105")if not I then return nil end local O=I:FindFirstChild("\084\111\117\099\104\067\111\110\116\114\111\108\070\114\097\109\101")if not O then return nil end a=O:FindFirstChild("\068\121\110\097\109\105\099\084\104\117\109\098\115\116\105\099\107\070\114\097\109\101")return a end end local w=nil local u=nil local function P()if UserInputService.TouchEnabled then if w then w:Disconnect()w=nil end local I=workspace.CurrentCamera if I then local O=I.ViewportSize PortraitMode=O.X<O.Y layoutGestureArea(PortraitMode)DefaultZoom=PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM w=(I:GetPropertyChangedSignal("\086\105\101\119\112\111\114\116\083\105\122\101")):Connect(function()O=I.ViewportSize PortraitMode=O.X<O.Y layoutGestureArea(PortraitMode)DefaultZoom=PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM end)end end end u=(workspace:GetPropertyChangedSignal("\067\117\114\114\101\110\116\067\097\109\101\114\097")):Connect(P)if workspace.CurrentCamera then P()end function I.GetShiftLock(I)return ShiftLockController:IsShiftLocked()end function I.GetHumanoid(I)local O=PlayersService.LocalPlayer return findPlayerHumanoid(O)end function I.GetHumanoidRootPart(O)local o=I:GetHumanoid()return o and o.Torso end function I.GetRenderCFrame(O,I)o(I)end local A=Enum.HumanoidStateType.Dead local function D(I,O)if O==A then local O=I.Parent if O then return O:FindFirstChild("\072\101\097\100")or I.Torso else return I.Torso end else return I.Torso end end local n=Enum.HumanoidStateType.Dead function I.GetSubjectPosition(O)local s=nil local Y=workspace.CurrentCamera local c=Y and Y.CameraSubject if c then if c:IsA("\072\117\109\097\110\111\105\100")then local O=c:GetState()if VRService.VREnabled and(O==A and c==I.lastSubject)then s=I.lastSubjectPosition else local I=D(c,O)if I and I:IsA("\066\097\115\101\080\097\114\116")then local Y=o(I)local V=ZERO_VECTOR3 if O~=A then V=c.RigType==Enum.HumanoidRigType.R15 and v or HEAD_OFFSET end if PortraitMode then V=V+Vector3.new(0,PORTRAIT_MODE_CAMERA_OFFSET,0)end s=Y.p+Y:vectorToWorldSpace(V+c.CameraOffset)end end elseif c:IsA("\086\101\104\105\099\108\101\083\101\097\116")then local I=o(c)local O=SEAT_OFFSET if VRService.VREnabled then O=VR_SEAT_OFFSET end s=I.p+I:vectorToWorldSpace(O)elseif c:IsA("\083\107\097\116\101\098\111\097\114\100\080\108\097\116\102\111\114\109")then local I=o(c)s=I.p+SEAT_OFFSET elseif c:IsA("\066\097\115\101\080\097\114\116")then local I=o(c)s=I.p elseif c:IsA("\077\111\100\101\108")then s=(c:GetModelCFrame()).p end end I.lastSubject=c I.lastSubjectPosition=s return s end function I.ResetCameraLook(I) end function I.GetCameraLook(I)return workspace.CurrentCamera and workspace.CurrentCamera.CoordinateFrame.lookVector or Vector3.new(0,0,1)end function I.GetCameraZoom(O)if I.currentZoom==nil then local O=PlayersService.LocalPlayer I.currentZoom=O and clamp(O.CameraMinZoomDistance,O.CameraMaxZoomDistance,I.DefaultZoom)or I.DefaultZoom end return I.currentZoom end function I.GetCameraActualZoom(I)local O=workspace.CurrentCamera if O then return(O.CoordinateFrame.p-O.Focus.p).magnitude end end function I.GetCameraHeight(O)if VRService.VREnabled and not I:IsInFirstPerson()then local O=I:GetCameraZoom()return math.sin(VR_ANGLE)*O end return 0 end function I.ViewSizeX(I)local O=1024 local o=workspace.CurrentCamera if o then O=o.ViewportSize.X end return O end function I.ViewSizeY(I)local O=768 local o=workspace.CurrentCamera if o then O=o.ViewportSize.Y end return O end local z=math.asin local K=math.atan2 local y=math.floor local W=math.max local d=math.pi local H=Vector2.new local J=Vector3.new local S=CFrame.Angles local Q=CFrame.new function I.ScreenTranslationToAngle(o,O)local v=I:ViewSizeX()local s=I:ViewSizeY()local Y=O.x/v local c=O.y/s return H(Y,c)end function I.MouseTranslationToAngle(O,I)local o=I.x/1920 local v=I.y/1200 return H(o,v)end function I.RotateVector(o,I,O)local v=Q(ZERO_VECTOR3,I)local s=((S(0,-O.x,0)*v)*S(-O.y,0,0)).lookVector return s,H(O.x,O.y)end function I.RotateCamera(o,I,O)if VRService.VREnabled then local v,s=o:RotateVector(I,Vector2.new(O.x,0))return(J(v.x,0,v.z)).unit,s else local v=z(I.y)local s=clamp(-MAX_Y+v,-MIN_Y+v,O.y)return o:RotateVector(I,H(O.x,s))end end function I.IsInFirstPerson(I)return s end function I.UpdateMouseBehavior(I)if s or I:GetShiftLock()then pcall(function()GameSettings.RotationType=Enum.RotationType.CameraRelative end)if UserInputService.MouseBehavior~=Enum.MouseBehavior.LockCenter then UserInputService.MouseBehavior=Enum.MouseBehavior.LockCenter end else pcall(function()GameSettings.RotationType=Enum.RotationType.MovementRelative end)if Y or c then UserInputService.MouseBehavior=Enum.MouseBehavior.LockCurrentPosition else UserInputService.MouseBehavior=Enum.MouseBehavior.Default end end end function I.ZoomCamera(o,O)local v=PlayersService.LocalPlayer if v then if v.CameraMode==Enum.CameraMode.LockFirstPerson then I.currentZoom=0 else I.currentZoom=clamp(v.CameraMinZoomDistance,v.CameraMaxZoomDistance,O)end end s=o:GetCameraZoom()<2 ShiftLockController:SetIsInFirstPerson(s)o:UpdateMouseBehavior()return o:GetCameraZoom()end function I.rk4Integrator(v,I,O,o)local s=O<0 and-1 or 1 local function Y(I,O)local o=s*W(1,I/3.3+.5)return o end local c=I local V=O local e=Y(c,V)local b=c+V*(o/2)local U=V+e*(o/2)local a=Y(b,U)local C=c+U*(o/2)local w=V+a*(o/2)local u=Y(C,w)local P=c+w*o local A=V+u*o local D=Y(P,A)local n=I+(((V+2*U)+2*w)+A)*(o/6)local z=O+(((e+2*a)+2*u)+D)*(o/6)return n,z end function I.ZoomCameraBy(o,O)local v=I:GetCameraActualZoom()if v then v=o:rk4Integrator(v,O,1)o:ZoomCamera(v)end return o:GetCameraZoom()end function I.ZoomCameraFixedBy(O,I)return O:ZoomCamera(O:GetCameraZoom()+I)end function I.Update(I) end function I.ApplyVRTransform(O)if not VRService.VREnabled then return end local o=PlayersService.LocalPlayer if not(o and(o.Character and(o.Character:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")and o.Character.HumanoidRootPart:FindFirstChild("\082\111\111\116\074\111\105\110\116"))))then return end local v=workspace.CurrentCamera local s=v.CameraSubject local Y=s and s:IsA("\086\101\104\105\099\108\101\083\101\097\116")if I:IsInFirstPerson()and not Y then local I=VRService:GetUserCFrame(Enum.UserCFrame.Head)local O=I-I.p local v=o.Character.HumanoidRootPart.RootJoint v.C0=CFrame.new(O:vectorToObjectSpace(I.p))*CFrame.new(0,0,0,-1,0,0,0,0,1,0,1,0)else local I=o.Character.HumanoidRootPart.RootJoint I.C0=CFrame.new(0,0,0,-1,0,0,0,0,1,0,1,0)end end local m=true local R=0 function I.ShouldUseVRRotation(I)if not VRService.VREnabled then return false end if not m and tick()-R<1 then return false end local O,o=pcall(function()return StarterGui:GetCore("\086\082\082\111\116\097\116\105\111\110\073\110\116\101\110\115\105\116\121")end)m=O and o~=nil R=tick()return O and(o~=nil and o~="\083\109\111\111\116\104")end function I.GetVRRotationInput(I)local O=ZERO_VECTOR2 local o=StarterGui:GetCore("\086\082\082\111\116\097\116\105\111\110\073\110\116\101\110\115\105\116\121")local v=I.GamepadPanningCamera or ZERO_VECTOR2 local s=tick()-e>=I:GetRepeatDelayValue(o)if math.abs(v.x)>=I:GetActivateValue()then if s or not b[Enum.KeyCode.Thumbstick2]then local s=1 if v.x<0 then s=-1 end O=O+I:GetRotateAmountValue(o)*s b[Enum.KeyCode.Thumbstick2]=true end elseif math.abs(v.x)<I:GetActivateValue()-.1 then b[Enum.KeyCode.Thumbstick2]=nil end if I.TurningLeft then if s or not b[Enum.KeyCode.Left]then O=O-I:GetRotateAmountValue(o)b[Enum.KeyCode.Left]=true end else b[Enum.KeyCode.Left]=nil end if I.TurningRight then if s or not b[Enum.KeyCode.Right]then O=O+I:GetRotateAmountValue(o)b[Enum.KeyCode.Right]=true end else b[Enum.KeyCode.Right]=nil end if O~=ZERO_VECTOR2 then e=tick()end return O end local p=Vector3.new(1,1,1)local X=nil local j=nil local h=false local M=nil local L=nil local g=nil local f=nil local T=nil local function t(I)if not I then p=Vector3.new(p.x,1,p.z)end if h then j=nil h=false end end local function k(I,O)if not h then X=I j=O p=Vector3.new(p.x,0,p.z)h=true end end local function G(I)v=R15_HEAD_OFFSET*I end local function B(I)if I.Name=="\066\111\100\121\072\101\105\103\104\116\083\099\097\108\101"and I:IsA("\078\117\109\098\101\114\086\097\108\117\101")then if heightScaleChangedConn then heightScaleChangedConn:disconnect()end heightScaleChangedConn=I.Changed:connect(G)G(I.Value)end end local function q(I)if I.Name=="\066\111\100\121\072\101\105\103\104\116\083\099\097\108\101"then G(1)if heightScaleChangedConn then heightScaleChangedConn:disconnect()heightScaleChangedConn=nil end end end local function Z()if M then M:disconnect()M=nil end if f then f:disconnect()f=nil end if T then T:disconnect()T=nil end if heightScaleChangedConn then heightScaleChangedConn:disconnect()heightScaleChangedConn=nil end local O=workspace.CurrentCamera and workspace.CurrentCamera.CameraSubject if j~=O then t()end if O and O:IsA("\072\117\109\097\110\111\105\100")then f=O.ChildAdded:connect(B)T=O.ChildRemoved:connect(q)for I,O in pairs(O:GetChildren())do B(O)end M=O.StateChanged:connect(function(o,v)if VRService.VREnabled and(v==Enum.HumanoidStateType.Jumping and not I:IsInFirstPerson())then k(I:GetSubjectPosition(),O)elseif v~=Enum.HumanoidStateType.Jumping and v~=Enum.HumanoidStateType.Freefall then t(true)end end)end end local function x()if L then L:disconnect()L=nil end local I=workspace.CurrentCamera if I then L=(I:GetPropertyChangedSignal("\067\097\109\101\114\097\083\117\098\106\101\099\116")):connect(Z)Z()end end function I.GetVRFocus(o,I,O)local v=nil local s=workspace.CurrentCamera local Y=o.LastCameraFocus or I if not h then p=Vector3.new(p.x,math.min(1,p.y+.42*O),p.z)end if h and(X and X.y>Y.y)then v=CFrame.new(Vector3.new(I.x,math.min(X.y,Y.y+5*O),I.z))else v=CFrame.new((Vector3.new(I.x,Y.y,I.z)):lerp(I,p.y))end if h then if o:IsInFirstPerson()then t()end if X and I.y<X.y-.5 then t()end end return v end local N=nil local l=nil local E=nil local r=nil local F={}local i=0 local I6={}local O6={}local o6=nil local v6=nil I.ZoomEnabled=true I.PanEnabled=true I.KeyPanEnabled=true local function s6(I,o)local v=C()if not touchWorkspaceEventEnabled and not U or O(I.Position,GestureArea)and(not v or not O(I.Position,v))then F[I]=o if not o then I6[I]=I.Position O6[I]=tick()i=i+1 end end end local function Y6(O,o)if F[O]==nil then if U then return end F[O]=o if not o then i=i+1 end end if i==1 then if F[O]==false then E=E or I:GetCameraLook()N=N or O.Position l=l or N I.UserPanningTheCamera=true local o=O.Position-l o=Vector2.new(o.X,o.Y*GameSettings:GetCameraYInvertValue())if I.PanEnabled then local O=I:ScreenTranslationToAngle(o)*TOUCH_SENSITIVTY I.RotateInput=I.RotateInput+O end l=O.Position end else E=nil N=nil l=nil I.UserPanningTheCamera=false end if i==2 then local O={}for I,o in pairs(F)do if not o then table.insert(O,I)end end if#O==2 then local o=(O[1].Position-O[2].Position).magnitude if o6 and v6 then local O=o/W(.01,o6)local v=clamp(.1,10,O)if I.ZoomEnabled then I:ZoomCamera(v6/v)end else o6=o v6=I:GetCameraActualZoom()end end else o6=nil v6=nil end end local function c6(O)if O then local o=(O.CFrame.lookVector-Vector3.new(0,math.sin(math.rad(DEFAULT_CAMERA_ANGLE),0))).unit local v=findAngleBetweenXZVectors(o,I:GetCameraLook())local s=math.asin((I:GetCameraLook()).y)-math.asin(o.y)if not IsFinite(v)then v=0 end if not IsFinite(s)then s=0 end return Vector2.new(v,s)end return nil end local V6=nil if not touchWorkspaceEventEnabled then V6=function(O)if U and not IsAToolEquipped then if r and tick()-r<MAX_TIME_FOR_DOUBLE_TAP then local O={from=I:GetCameraZoom(),to=DefaultZoom,start=tick(),duration=.2,func=function(O,o,v)I:ZoomCamera(O+(o-O)*v)return o end}V.Zoom=O else local O=I:GetHumanoid()if O then local o=PlayersService.LocalPlayer if o and o.Character then if O and O.Torso then local o={from=I.RotateInput,to=c6(O.Torso);start=tick();duration=.2;func=function(o,v,s)v=c6(O.Torso)if v then I.RotateInput=o+(v-o)*s end return v end}V.Rotate=o I.LastCameraTransform=nil end end end end r=tick()end end end local function e6(I)if I6[I]then local O=(I6[I]-I.Position).magnitude if O<MAX_TAP_POS_DELTA then local O=O6[I]-tick()if O<MAX_TAP_TIME_DELTA then return true end end end return false end local function b6(O,o)if F[O]==false then if i==1 then E=nil N=nil l=nil I.UserPanningTheCamera=false if not touchWorkspaceEventEnabled and e6(O)then V6(O.Position)end elseif i==2 then o6=nil v6=nil end end if F[O]~=nil and F[O]==false then i=i-1 end F[O]=nil I6[O]=nil O6[O]=nil end local function U6(O,o)if o then return end I:UpdateMouseBehavior()E=E or I:GetCameraLook()N=N or O.Position l=l or N I.UserPanningTheCamera=true end local function a6(O,o)I:UpdateMouseBehavior()if not(Y or c)then E=nil N=nil l=nil I.UserPanningTheCamera=false end end local function C6(I,O)if O then return end Y=true U6(I,O)end local function w6(I,O)Y=false a6(I,O)end local function u6(I,O)if O then return end c=true U6(I,O)end local function P6(I,O)c=false a6(I,O)end local function A6(O,o)if not hasGameLoaded and VRService.VREnabled then return end local v=O.Delta v=Vector2.new(v.X,v.Y*GameSettings:GetCameraYInvertValue())if N and(l and E)then local o=l+O.Delta local s=o-N if I.PanEnabled then local O=I:MouseTranslationToAngle(v)*MOUSE_SENSITIVITY I.RotateInput=I.RotateInput+O end l=o elseif I:IsInFirstPerson()or I:GetShiftLock()then if I.PanEnabled then local O=I:MouseTranslationToAngle(v)*MOUSE_SENSITIVITY I.RotateInput=I.RotateInput+O end end end local function D6(O,o)if not hasGameLoaded and VRService.VREnabled then return end if not o then if I.ZoomEnabled then I:ZoomCameraBy(clamp(-1,1,-O.Position.Z)*1.4)end end end local function n6(I)return y(I+.5)end local z6=d/4 local function K6(I,O,o)if I~=ZERO_VECTOR3 then I=I.unit local v=K(I.z,I.x)local s=n6((K(I.z,I.x)+O)/o)*o return s-v end return 0 end local function y6(O,o)if not hasGameLoaded and VRService.VREnabled then return end if o then return end if I.ZoomEnabled then if O.KeyCode==Enum.KeyCode.I then I:ZoomCameraBy(-5)elseif O.KeyCode==Enum.KeyCode.O then I:ZoomCameraBy(5)end end if E==nil and I.KeyPanEnabled then if O.KeyCode==Enum.KeyCode.Left then I.TurningLeft=true elseif O.KeyCode==Enum.KeyCode.Right then I.TurningRight=true elseif O.KeyCode==Enum.KeyCode.Comma then local O=K6(I:GetCameraLook()*Vector3.new(1,0,1),-z6*.75,z6)if O~=0 then I.RotateInput=I.RotateInput+Vector2.new(O,0)I.LastUserPanCamera=tick()I.LastCameraTransform=nil end elseif O.KeyCode==Enum.KeyCode.Period then local O=K6(I:GetCameraLook()*Vector3.new(1,0,1),z6*.75,z6)if O~=0 then I.RotateInput=I.RotateInput+Vector2.new(O,0)I.LastUserPanCamera=tick()I.LastCameraTransform=nil end elseif O.KeyCode==Enum.KeyCode.PageUp then I.RotateInput=I.RotateInput+Vector2.new(0,math.rad(15))I.LastCameraTransform=nil elseif O.KeyCode==Enum.KeyCode.PageDown then I.RotateInput=I.RotateInput+Vector2.new(0,math.rad(-15))I.LastCameraTransform=nil end end end local function W6(O,o)if O.KeyCode==Enum.KeyCode.Left then I.TurningLeft=false elseif O.KeyCode==Enum.KeyCode.Right then I.TurningRight=false end end local d6=nil local H6=.7 local J6=0 local S6=6 local Q6=4 local m6=Vector2.new(0,0)local R6=.65 local p6=nil local X6=.35 local j6=.8 local function h6(I)I=clamp(-1,1,I)if I>=0 then return(X6*I)/((X6-I)+1)end return-((j6*-I)/((j6+I)+1))end local M6=.1 local function L6(I)return(1+M6)*(2*math.abs(I)-1)-M6 end local function g6(I)return I/2+.5 end local function f6(I)local function O(I)local O=1 if I<0 then O=-1 end local o=g6(h6(L6(math.abs(I))))o=o*O return clamp(-1,1,o)end return H(O(I.x),O(I.y))end function I.UpdateGamepad(O)local o=I.GamepadPanningCamera if o and(hasGameLoaded or not VRService.VREnabled)then o=f6(o)local O=tick()if o.X~=0 or o.Y~=0 then I.userPanningTheCamera=true elseif o==ZERO_VECTOR2 then d6=nil if m6==ZERO_VECTOR2 then J6=0 end end local v=0 if d6 then if VRService.VREnabled then J6=Q6 else local I=(O-d6)*10 J6=J6+S6*((I*I)/H6)if J6>S6 then J6=S6 end if p6 then local I=(o-m6)/(O-d6)local v=(I-p6).magnitude if v>12 then J6=J6*(20/v)if J6>S6 then J6=S6 end end end end local I,s=pcall(function()return GameSettings.GamepadCameraSensitivity end)v=I and s*J6 or J6 p6=(o-m6)/(O-d6)end m6=o d6=O return H(o.X*v,((o.Y*v)*R6)*GameSettings:GetCameraYInvertValue())end return ZERO_VECTOR2 end local T6,t6,k6,G6,B6,q6,Z6,x6=nil,nil,nil,nil,nil,nil,nil,nil function I.DisconnectInputEvents(O)if T6 then T6:disconnect()T6=nil end if t6 then t6:disconnect()t6=nil end if k6 then k6:disconnect()k6=nil end if G6 then G6:disconnect()G6=nil end if B6 then B6:disconnect()B6=nil end if q6 then q6:disconnect()q6=nil end if Z6 then Z6:disconnect()Z6=nil end if M then M:disconnect()M=nil end if g then g:disconnect()g=nil end if x6 then x6:disconnect()x6=nil end I.TurningLeft=false I.TurningRight=false I.LastCameraTransform=nil O.LastSubjectCFrame=nil I.UserPanningTheCamera=false I.RotateInput=Vector2.new()I.GamepadPanningCamera=Vector2.new(0,0)N=nil l=nil E=nil Y=false c=false F={}i=0 o6=nil v6=nil if UserInputService.MouseBehavior~=Enum.MouseBehavior.LockCenter then UserInputService.MouseBehavior=Enum.MouseBehavior.Default end end function I.ResetInputStates(O)Y=false c=false a6()if UserInputService.TouchEnabled then for I,O in pairs(F)do F[I]=nil end E=nil N=nil l=nil I.UserPanningTheCamera=false o6=nil v6=nil i=0 end end function I.getGamepadPan(O,o,v)if o==Enum.UserInputState.Cancel then I.GamepadPanningCamera=ZERO_VECTOR2 return end if v.UserInputType==I.activeGamepad and v.KeyCode==Enum.KeyCode.Thumbstick2 then local O=Vector2.new(v.Position.X,-v.Position.Y)if O.magnitude>THUMBSTICK_DEADZONE then I.GamepadPanningCamera=H(v.Position.X,-v.Position.Y)else I.GamepadPanningCamera=ZERO_VECTOR2 end end end function I.doGamepadZoom(O,o,v)if v.UserInputType==I.activeGamepad and(v.KeyCode==Enum.KeyCode.ButtonR3 and o==Enum.UserInputState.Begin)then if I.ZoomEnabled then if I:GetCameraZoom()>.5 then I:ZoomCamera(0)else I:ZoomCamera(10)end end end end function I.BindGamepadInputActions(O)ContextActionService:BindAction("\082\111\111\116\067\097\109\071\097\109\101\112\097\100\080\097\110",I.getGamepadPan,false,Enum.KeyCode.Thumbstick2)ContextActionService:BindAction("\082\111\111\116\067\097\109\071\097\109\101\112\097\100\090\111\111\109",I.doGamepadZoom,false,Enum.KeyCode.ButtonR3)end function I.ConnectInputEvents(O)T6=UserInputService.InputBegan:connect(function(I,O)if I.UserInputType==Enum.UserInputType.Touch then s6(I,O)elseif I.UserInputType==Enum.UserInputType.MouseButton2 then C6(I,O)elseif I.UserInputType==Enum.UserInputType.MouseButton3 then u6(I,O)end if I.UserInputType==Enum.UserInputType.Keyboard then y6(I,O)end end)t6=UserInputService.InputChanged:connect(function(I,O)if I.UserInputType==Enum.UserInputType.Touch then Y6(I,O)elseif I.UserInputType==Enum.UserInputType.MouseMovement then A6(I,O)elseif I.UserInputType==Enum.UserInputType.MouseWheel then D6(I,O)end end)k6=UserInputService.InputEnded:connect(function(I,O)if I.UserInputType==Enum.UserInputType.Touch then b6(I,O)elseif I.UserInputType==Enum.UserInputType.MouseButton2 then w6(I,O)elseif I.UserInputType==Enum.UserInputType.MouseButton3 then P6(I,O)end if I.UserInputType==Enum.UserInputType.Keyboard then W6(I,O)end end)G6=GuiService.MenuOpened:connect(function()I:ResetInputStates()end)g=(workspace:GetPropertyChangedSignal("\067\117\114\114\101\110\116\067\097\109\101\114\097")):Connect(x)if workspace.CurrentCamera then x()end B6=ShiftLockController.OnShiftLockToggled.Event:connect(function()I:UpdateMouseBehavior()end)I.RotateInput=Vector2.new()I.activeGamepad=nil local function o()local O=UserInputService:GetConnectedGamepads()if#O>0 then for o=1,#O,1 do if I.activeGamepad==nil then I.activeGamepad=O[o]elseif O[o].Value<I.activeGamepad.Value then I.activeGamepad=O[o]end end end if I.activeGamepad==nil then I.activeGamepad=Enum.UserInputType.Gamepad1 end end q6=UserInputService.GamepadDisconnected:connect(function(O)if I.activeGamepad~=O then return end I.activeGamepad=nil o()end)Z6=UserInputService.GamepadConnected:connect(function(O)if I.activeGamepad==nil then o()end end)O:BindGamepadInputActions()o()O:UpdateMouseBehavior()end function I.ProcessTweens(I)for I,O in pairs(V)do local o=math.min(1.0,(tick()-O.start)/O.duration)O.to=O.func(O.from,O.to,o)if math.abs(1-o)<.0001 then V[I]=nil end end end function I.SetEnabled(O,I)if I~=O.Enabled then O.Enabled=I if O.Enabled then O:ConnectInputEvents()O.cframe=workspace.CurrentCamera.CFrame else O:DisconnectInputEvents()end end end local function N6(O)O.Changed:connect(function(O)if I.Enabled then if O=="\067\097\109\101\114\097\077\111\100\101"or O=="\067\097\109\101\114\097\077\097\120\090\111\111\109\068\105\115\116\097\110\099\101"or O=="\067\097\109\101\114\097\077\105\110\090\111\111\109\068\105\115\116\097\110\099\101"then I:ZoomCameraFixedBy(0)end end end)local function o(o)local v=findPlayerHumanoid(O)local s=tick()while tick()-s<.3 and(v==nil or v.Torso==nil)do wait()v=findPlayerHumanoid(O)end if v and(v.Torso and O.Character==o)then local O=(v.Torso.CFrame.lookVector-Vector3.new(0,math.sin(math.rad(DEFAULT_CAMERA_ANGLE)),0)).unit local o=findAngleBetweenXZVectors(O,I:GetCameraLook())local s=math.asin((I:GetCameraLook()).y)-math.asin(O.y)if not IsFinite(o)then o=0 end if not IsFinite(s)then s=0 end I.RotateInput=Vector2.new(o,s)I.LastCameraTransform=nil end wait()I:ZoomCamera(I.DefaultZoom)end O.CharacterAdded:connect(function(O)if I.Enabled or SetCameraOnSpawn then o(O)SetCameraOnSpawn=false end end)if O.Character then spawn(function()o(O.Character)end)end end if PlayersService.LocalPlayer then N6(PlayersService.LocalPlayer)end PlayersService.ChildAdded:connect(function(I)if I and PlayersService.LocalPlayer==I then N6(PlayersService.LocalPlayer)end end)local function l6()hasGameLoaded=true end spawn(function()if game:IsLoaded()then l6()else game.Loaded:wait()l6()end end)local function E6()if UserInputService.TouchEnabled then U=true end end local function r6()U=false end local function F6()if LocalPlayer.DevTouchMovementMode==Enum.DevTouchMovementMode.UserChoice then if GameSettings.TouchMovementMode.Name=="\068\121\110\097\109\105\099\084\104\117\109\098\115\116\105\099\107"then E6()else r6()end end end local function i6()if LocalPlayer.DevTouchMovementMode.Name=="\068\121\110\097\109\105\099\084\104\117\109\098\115\116\105\099\107"then E6()else F6()end end if PlayersService.LocalPlayer then PlayersService.LocalPlayer.Changed:Connect(function(I)if I=="\068\101\118\084\111\117\099\104\077\111\118\101\109\101\110\116\077\111\100\101"then i6()end end)i6()end GameSettings.Changed:Connect(function(I)if I=="\084\111\117\099\104\077\111\118\101\109\101\110\116\077\111\100\101"then F6()end end)F6()GameSettings:SetCameraYInvertVisible()pcall(function()GameSettings:SetGamepadCameraSensitivityVisible()end)return I end return v

local function OnCharacterAdded(character)
	if UserInputService.TouchEnabled then
		for _, child in ipairs(LocalPlayer.Character:GetChildren()) do
			if child:IsA("Tool") then
				IsAToolEquipped = true
			end
		end
		character.ChildAdded:Connect(function(child)
			if child:IsA("Tool") then
				IsAToolEquipped = true
			end
		end)
		character.ChildRemoved:Connect(function(child)
			if child:IsA("Tool") then
				IsAToolEquipped = false
			end
		end)

		if PlayerGui then
			local TouchGui = PlayerGui:FindFirstChild("TouchGui")
			if TouchGui and TouchGui:WaitForChild("GestureArea", 0.5) then
				GestureArea = TouchGui.GestureArea
				GestureAreaManagedByControlScript = true
			else
				GestureAreaManagedByControlScript = false
				local ScreenGui = Instance.new("ScreenGui")
				ScreenGui.Name = "GestureArea"
				ScreenGui.Parent = PlayerGui
				
				GestureArea = Instance.new("Frame")
				GestureArea.BackgroundTransparency = 1.0
				GestureArea.Visible = true
				GestureArea.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
				layoutGestureArea(PortraitMode)
				GestureArea.Parent = ScreenGui
			end
		end
	end
end

if LocalPlayer then
	if LocalPlayer.Character ~= nil then
		OnCharacterAdded(LocalPlayer.Character)
	end
	LocalPlayer.CharacterAdded:connect(function(character)
		OnCharacterAdded(character)
	end)
end
	
	
local function positionIntersectsGuiObject(position, guiObject)
	if position.X < guiObject.AbsolutePosition.X + guiObject.AbsoluteSize.X
		and position.X > guiObject.AbsolutePosition.X
		and position.Y < guiObject.AbsolutePosition.Y + guiObject.AbsoluteSize.Y
		and position.Y > guiObject.AbsolutePosition.Y then
		return true
	end
	return false
end	

local function GetRenderCFrame(part)
	return part:GetRenderCFrame()
end

local function CreateCamera()
	local this = {}
	local R15HeadHeight = R15_HEAD_OFFSET
	function this:GetActivateValue()
		return 0.7
	end
	
	function this:IsPortraitMode()
		return PortraitMode
	end	
	
	function this:GetRotateAmountValue(vrRotationIntensity)
		vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
		if vrRotationIntensity then
			if vrRotationIntensity == "Low" then
				return VR_LOW_INTENSITY_ROTATION
			elseif vrRotationIntensity == "High" then
				return VR_HIGH_INTENSITY_ROTATION
			end
		end
		return ZERO_VECTOR2
	end
	function this:GetRepeatDelayValue(vrRotationIntensity)
		vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
		if vrRotationIntensity then
			if vrRotationIntensity == "Low" then
				return VR_LOW_INTENSITY_REPEAT
			elseif vrRotationIntensity == "High" then
				return VR_HIGH_INTENSITY_REPEAT
			end
		end
		return 0
	end
	
	this.ShiftLock = false
	this.Enabled = false
	local isFirstPerson = false
	local isRightMouseDown = false
	local isMiddleMouseDown = false
	this.RotateInput = ZERO_VECTOR2
	this.DefaultZoom = LANDSCAPE_DEFAULT_ZOOM
	this.activeGamepad = nil
	
	local tweens = {}

	this.lastSubject = nil
	this.lastSubjectPosition = Vector3.new(0, 5, 0)

	local lastVRRotation = 0
	local vrRotateKeyCooldown = {}
	
	local isDynamicThumbstickEnabled = false
	local dynamicThumbstickFrame = nil
	
	local function getDynamicThumbstickFrame()
		if dynamicThumbstickFrame and dynamicThumbstickFrame:IsDescendantOf(game) then
			return dynamicThumbstickFrame
		else
			local touchGui = PlayerGui:FindFirstChild("TouchGui")
			if not touchGui then return nil end
			
			local touchControlFrame = touchGui:FindFirstChild("TouchControlFrame")
			if not touchControlFrame then return nil end
			
			dynamicThumbstickFrame = touchControlFrame:FindFirstChild("DynamicThumbstickFrame")
			return dynamicThumbstickFrame
		end
	end
	
	-- Check for changes in ViewportSize to decide if PortraitMode
	local CameraChangedConn = nil
	local workspaceCameraChangedConn = nil
	local function onWorkspaceCameraChanged()
		if UserInputService.TouchEnabled then
			if CameraChangedConn then
				CameraChangedConn:Disconnect()
				CameraChangedConn = nil
			end
			local newCamera = workspace.CurrentCamera
			if newCamera then
				local size = newCamera.ViewportSize
				PortraitMode = size.X < size.Y
				layoutGestureArea(PortraitMode)
				DefaultZoom = PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM
				CameraChangedConn = newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
					size = newCamera.ViewportSize
					PortraitMode = size.X < size.Y
					layoutGestureArea(PortraitMode)
					DefaultZoom = PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM
				end)
			end
		end
	end
	workspaceCameraChangedConn = workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(onWorkspaceCameraChanged)
	if workspace.CurrentCamera then
		onWorkspaceCameraChanged()
	end
		
	
	function this:GetShiftLock()
		return ShiftLockController:IsShiftLocked()
	end

	function this:GetHumanoid()
		local player = PlayersService.LocalPlayer
		return findPlayerHumanoid(player)
	end

	function this:GetHumanoidRootPart()
		local humanoid = this:GetHumanoid()
		return humanoid and humanoid.Torso
	end

	function this:GetRenderCFrame(part)
		GetRenderCFrame(part)
	end
	
	local STATE_DEAD = Enum.HumanoidStateType.Dead
	
	-- HumanoidRootPart when alive, Head part when dead
	local function getHumanoidPartToFollow(humanoid, humanoidStateType)
		if humanoidStateType == STATE_DEAD then
			local character = humanoid.Parent
			if character then
				return character:FindFirstChild("Head") or humanoid.Torso
			else
				return humanoid.Torso
			end
		else
			return humanoid.Torso
		end
	end

	local HUMANOID_STATE_DEAD = Enum.HumanoidStateType.Dead
	
	function this:GetSubjectPosition()
		local result = nil
		local camera = workspace.CurrentCamera
		local cameraSubject = camera and camera.CameraSubject
		if cameraSubject then
			if cameraSubject:IsA('Humanoid') then
				local humanoidStateType = cameraSubject:GetState()
				if VRService.VREnabled and humanoidStateType == STATE_DEAD and cameraSubject == this.lastSubject then
					result = this.lastSubjectPosition
				else
					local humanoidRootPart = getHumanoidPartToFollow(cameraSubject, humanoidStateType)
					if humanoidRootPart and humanoidRootPart:IsA('BasePart') then
						local subjectCFrame = GetRenderCFrame(humanoidRootPart)
						local heightOffset = ZERO_VECTOR3
						if humanoidStateType ~= STATE_DEAD then
							heightOffset = cameraSubject.RigType == Enum.HumanoidRigType.R15 and R15HeadHeight or HEAD_OFFSET
						end

						if PortraitMode then
							heightOffset = heightOffset + Vector3.new(0, PORTRAIT_MODE_CAMERA_OFFSET, 0)
						end

						result = subjectCFrame.p +
							subjectCFrame:vectorToWorldSpace(heightOffset + cameraSubject.CameraOffset)
					end
				end	
			elseif cameraSubject:IsA('VehicleSeat') then
				local subjectCFrame = GetRenderCFrame(cameraSubject)
				local offset = SEAT_OFFSET
				if VRService.VREnabled then
					offset = VR_SEAT_OFFSET
				end
				result = subjectCFrame.p + subjectCFrame:vectorToWorldSpace(offset)
			elseif cameraSubject:IsA('SkateboardPlatform') then
				local subjectCFrame = GetRenderCFrame(cameraSubject)
				result = subjectCFrame.p + SEAT_OFFSET
			elseif cameraSubject:IsA('BasePart') then
				local subjectCFrame = GetRenderCFrame(cameraSubject)
				result = subjectCFrame.p
			elseif cameraSubject:IsA('Model') then
				result = cameraSubject:GetModelCFrame().p
			end
		end
		this.lastSubject = cameraSubject
		this.lastSubjectPosition = result
		return result
	end

	function this:ResetCameraLook()
	end

	function this:GetCameraLook()
		return workspace.CurrentCamera and workspace.CurrentCamera.CoordinateFrame.lookVector or Vector3.new(0,0,1)
	end

	function this:GetCameraZoom()
		if this.currentZoom == nil then
			local player = PlayersService.LocalPlayer
			this.currentZoom = player and clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, this.DefaultZoom) or this.DefaultZoom
		end
		return this.currentZoom
	end

	function this:GetCameraActualZoom()
		local camera = workspace.CurrentCamera
		if camera then
			return (camera.CoordinateFrame.p - camera.Focus.p).magnitude
		end
	end

	function this:GetCameraHeight()
		if VRService.VREnabled and not this:IsInFirstPerson() then
			local zoom = this:GetCameraZoom()
			return math.sin(VR_ANGLE) * zoom
		end
		return 0
	end

	function this:ViewSizeX()
		local result = 1024
		local camera = workspace.CurrentCamera
		if camera then
			result = camera.ViewportSize.X
		end
		return result
	end

	function this:ViewSizeY()
		local result = 768
		local camera = workspace.CurrentCamera
		if camera then
			result = camera.ViewportSize.Y
		end
		return result
	end

	
	local math_asin = math.asin
	local math_atan2 = math.atan2
	local math_floor = math.floor
	local math_max = math.max
	local math_pi = math.pi
	local Vector2_new = Vector2.new
	local Vector3_new = Vector3.new
	local CFrame_Angles = CFrame.Angles
	local CFrame_new = CFrame.new

	function this:ScreenTranslationToAngle(translationVector)
		local screenX = this:ViewSizeX()
		local screenY = this:ViewSizeY()
		local xTheta = (translationVector.x / screenX)
		local yTheta = (translationVector.y / screenY)
		return Vector2_new(xTheta, yTheta)
	end

	function this:MouseTranslationToAngle(translationVector)
		local xTheta = (translationVector.x / 1920)
		local yTheta = (translationVector.y / 1200)
		return Vector2_new(xTheta, yTheta)
	end

	function this:RotateVector(startVector, xyRotateVector)
		local startCFrame = CFrame_new(ZERO_VECTOR3, startVector)
		local resultLookVector = (CFrame_Angles(0, -xyRotateVector.x, 0) * startCFrame * CFrame_Angles(-xyRotateVector.y,0,0)).lookVector
		return resultLookVector, Vector2_new(xyRotateVector.x, xyRotateVector.y)
	end

	function this:RotateCamera(startLook, xyRotateVector)
		if VRService.VREnabled then
			local yawRotatedVector, xyRotateVector = self:RotateVector(startLook, Vector2.new(xyRotateVector.x, 0))
			return Vector3_new(yawRotatedVector.x, 0, yawRotatedVector.z).unit, xyRotateVector
		else
			local startVertical = math_asin(startLook.y)
			local yTheta = clamp(-MAX_Y + startVertical, -MIN_Y + startVertical, xyRotateVector.y)
			return self:RotateVector(startLook, Vector2_new(xyRotateVector.x, yTheta))
		end
	end

	function this:IsInFirstPerson()
		return isFirstPerson
	end

	-- there are several cases to consider based on the state of input and camera rotation mode
	function this:UpdateMouseBehavior()
		-- first time transition to first person mode or shiftlock
		if isFirstPerson or self:GetShiftLock() then
			pcall(function() GameSettings.RotationType = Enum.RotationType.CameraRelative end)
			if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
				UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
			end
		else
			pcall(function() GameSettings.RotationType = Enum.RotationType.MovementRelative end)
			if isRightMouseDown or isMiddleMouseDown then
				UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
			else
				UserInputService.MouseBehavior = Enum.MouseBehavior.Default
			end
		end
	end

	function this:ZoomCamera(desiredZoom)
		local player = PlayersService.LocalPlayer
		if player then
			if player.CameraMode == Enum.CameraMode.LockFirstPerson then
				this.currentZoom = 0
			else
				this.currentZoom = clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, desiredZoom)
			end
		end

		isFirstPerson = self:GetCameraZoom() < 2

		ShiftLockController:SetIsInFirstPerson(isFirstPerson)
		-- set mouse behavior
		self:UpdateMouseBehavior()
		return self:GetCameraZoom()
	end

	function this:rk4Integrator(position, velocity, t)
		local direction = velocity < 0 and -1 or 1
		local function acceleration(p, v)
			local accel = direction * math_max(1, (p / 3.3) + 0.5)
			return accel
		end

		local p1 = position
		local v1 = velocity
		local a1 = acceleration(p1, v1)
		local p2 = p1 + v1 * (t / 2)
		local v2 = v1 + a1 * (t / 2)
		local a2 = acceleration(p2, v2)
		local p3 = p1 + v2 * (t / 2)
		local v3 = v1 + a2 * (t / 2)
		local a3 = acceleration(p3, v3)
		local p4 = p1 + v3 * t
		local v4 = v1 + a3 * t
		local a4 = acceleration(p4, v4)

		local positionResult = position + (v1 + 2 * v2 + 2 * v3 + v4) * (t / 6)
		local velocityResult = velocity + (a1 + 2 * a2 + 2 * a3 + a4) * (t / 6)
		return positionResult, velocityResult
	end

	function this:ZoomCameraBy(zoomScale)
		local zoom = this:GetCameraActualZoom()
		
		if zoom then
			-- Can break into more steps to get more accurate integration
            zoom = self:rk4Integrator(zoom, zoomScale, 1)
			self:ZoomCamera(zoom)
		end
		return self:GetCameraZoom()
	end

	function this:ZoomCameraFixedBy(zoomIncrement)
		return self:ZoomCamera(self:GetCameraZoom() + zoomIncrement)
	end

	function this:Update()
	end

	----- VR STUFF ------

	function this:ApplyVRTransform()
		if not VRService.VREnabled then
			return
		end
		--we only want this to happen in first person VR
		local player = PlayersService.LocalPlayer
		if not (player and player.Character
				and player.Character:FindFirstChild("HumanoidRootPart")
				and player.Character.HumanoidRootPart:FindFirstChild("RootJoint")) then
			return
		end

		local camera = workspace.CurrentCamera
		local cameraSubject = camera.CameraSubject
		local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')

		if this:IsInFirstPerson() and not isInVehicle then
			local vrFrame = VRService:GetUserCFrame(Enum.UserCFrame.Head)
			local vrRotation = vrFrame - vrFrame.p
			local rootJoint = player.Character.HumanoidRootPart.RootJoint
			rootJoint.C0 = CFrame.new(vrRotation:vectorToObjectSpace(vrFrame.p)) * CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
		else
			local rootJoint = player.Character.HumanoidRootPart.RootJoint
			rootJoint.C0 = CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
		end
	end

	local vrRotationIntensityExists = true
	local lastVrRotationCheck = 0
	function this:ShouldUseVRRotation()
		if not VRService.VREnabled then
			return false
		end
		if not vrRotationIntensityExists and tick() - lastVrRotationCheck < 1 then return false end

		local success, vrRotationIntensity = pcall(function() return StarterGui:GetCore("VRRotationIntensity") end)
		vrRotationIntensityExists = success and vrRotationIntensity ~= nil
		lastVrRotationCheck = tick()

		return success and vrRotationIntensity ~= nil and vrRotationIntensity ~= "Smooth"
	end

	function this:GetVRRotationInput()
		local vrRotateSum = ZERO_VECTOR2

		local vrRotationIntensity = StarterGui:GetCore("VRRotationIntensity")

		local vrGamepadRotation = self.GamepadPanningCamera or ZERO_VECTOR2
		local delayExpired = (tick() - lastVRRotation) >= self:GetRepeatDelayValue(vrRotationIntensity)

		if math.abs(vrGamepadRotation.x) >= self:GetActivateValue() then
			if (delayExpired or not vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2]) then
				local sign = 1
				if vrGamepadRotation.x < 0 then
					sign = -1
				end
				vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity) * sign
				vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = true
			end
		elseif math.abs(vrGamepadRotation.x) < self:GetActivateValue() - 0.1 then
			vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = nil
		end
		if self.TurningLeft then
			if delayExpired or not vrRotateKeyCooldown[Enum.KeyCode.Left] then
				vrRotateSum = vrRotateSum - self:GetRotateAmountValue(vrRotationIntensity)
				vrRotateKeyCooldown[Enum.KeyCode.Left] = true
			end
		else
			vrRotateKeyCooldown[Enum.KeyCode.Left] = nil
		end
		if self.TurningRight then
			if (delayExpired or not vrRotateKeyCooldown[Enum.KeyCode.Right]) then
				vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity)
				vrRotateKeyCooldown[Enum.KeyCode.Right] = true
			end
		else
			vrRotateKeyCooldown[Enum.KeyCode.Right] = nil
		end

		if vrRotateSum ~= ZERO_VECTOR2 then
			lastVRRotation = tick()
		end

		return vrRotateSum
	end

	local cameraTranslationConstraints = Vector3.new(1, 1, 1)
	local humanoidJumpOrigin = nil
	local trackingHumanoid = nil
	local cameraFrozen = false
	local subjectStateChangedConn = nil
	local cameraSubjectChangedConn = nil
	local workspaceChangedConn = nil
	local humanoidChildAddedConn = nil
	local humanoidChildRemovedConn = nil

	local function cancelCameraFreeze(keepConstraints)
		if not keepConstraints then
			cameraTranslationConstraints = Vector3.new(cameraTranslationConstraints.x, 1, cameraTranslationConstraints.z)
		end
		if cameraFrozen then
			trackingHumanoid = nil
			cameraFrozen = false
		end
	end

	local function startCameraFreeze(subjectPosition, humanoidToTrack)
		if not cameraFrozen then
			humanoidJumpOrigin = subjectPosition
			trackingHumanoid = humanoidToTrack
			cameraTranslationConstraints = Vector3.new(cameraTranslationConstraints.x, 0, cameraTranslationConstraints.z)
			cameraFrozen = true
		end
	end

	local function rescaleCameraOffset(newScaleFactor)
		R15HeadHeight = R15_HEAD_OFFSET*newScaleFactor
	end

	local function onHumanoidSubjectChildAdded(child)
		if child.Name == "BodyHeightScale" and child:IsA("NumberValue") then
			if heightScaleChangedConn then
				heightScaleChangedConn:disconnect()
			end
			heightScaleChangedConn = child.Changed:connect(rescaleCameraOffset)
			rescaleCameraOffset(child.Value)
		end
	end

	local function onHumanoidSubjectChildRemoved(child)
		if child.Name == "BodyHeightScale" then
			rescaleCameraOffset(1)
			if heightScaleChangedConn then
				heightScaleChangedConn:disconnect()
				heightScaleChangedConn = nil
			end
		end
	end

	local function onNewCameraSubject()
		if subjectStateChangedConn then
			subjectStateChangedConn:disconnect()
			subjectStateChangedConn = nil
		end
		if humanoidChildAddedConn then
			humanoidChildAddedConn:disconnect()
			humanoidChildAddedConn = nil
		end
		if humanoidChildRemovedConn then
			humanoidChildRemovedConn:disconnect()
			humanoidChildRemovedConn = nil
		end
		if heightScaleChangedConn then
			heightScaleChangedConn:disconnect()
			heightScaleChangedConn = nil
		end

		local humanoid = workspace.CurrentCamera and workspace.CurrentCamera.CameraSubject
		if trackingHumanoid ~= humanoid then
			cancelCameraFreeze()
		end
		if humanoid and humanoid:IsA('Humanoid') then
			humanoidChildAddedConn = humanoid.ChildAdded:connect(onHumanoidSubjectChildAdded)
			humanoidChildRemovedConn = humanoid.ChildRemoved:connect(onHumanoidSubjectChildRemoved)
			for _, child in pairs(humanoid:GetChildren()) do
				onHumanoidSubjectChildAdded(child)
			end

			subjectStateChangedConn = humanoid.StateChanged:connect(function(oldState, newState)
				if VRService.VREnabled and newState == Enum.HumanoidStateType.Jumping and not this:IsInFirstPerson() then
					startCameraFreeze(this:GetSubjectPosition(), humanoid)
				elseif newState ~= Enum.HumanoidStateType.Jumping and newState ~= Enum.HumanoidStateType.Freefall then
					cancelCameraFreeze(true)
				end
			end)
		end
	end

 	local function onCurrentCameraChanged()
		if cameraSubjectChangedConn then
			cameraSubjectChangedConn:disconnect()
			cameraSubjectChangedConn = nil
		end
  		local camera = workspace.CurrentCamera
  		if camera then
			cameraSubjectChangedConn = camera:GetPropertyChangedSignal("CameraSubject"):connect(onNewCameraSubject)
			onNewCameraSubject()
  		end
  	end

	function this:GetVRFocus(subjectPosition, timeDelta)
		local newFocus = nil

		local camera = workspace.CurrentCamera
		local lastFocus = self.LastCameraFocus or subjectPosition
		if not cameraFrozen then
			cameraTranslationConstraints = Vector3.new(cameraTranslationConstraints.x, math.min(1, cameraTranslationConstraints.y + 0.42 * timeDelta), cameraTranslationConstraints.z)
		end
		if cameraFrozen and humanoidJumpOrigin and humanoidJumpOrigin.y > lastFocus.y then
			newFocus = CFrame.new(Vector3.new(subjectPosition.x, math.min(humanoidJumpOrigin.y, lastFocus.y + 5 * timeDelta), subjectPosition.z))
		else
			newFocus = CFrame.new(Vector3.new(subjectPosition.x, lastFocus.y, subjectPosition.z):lerp(subjectPosition, cameraTranslationConstraints.y))
		end

		if cameraFrozen then
			-- No longer in 3rd person
			if self:IsInFirstPerson() then -- not VRService.VREnabled
				cancelCameraFreeze()
			end
			-- This case you jumped off a cliff and want to keep your character in view
			-- 0.5 is to fix floating point error when not jumping off cliffs
			if humanoidJumpOrigin and subjectPosition.y < (humanoidJumpOrigin.y - 0.5) then
				cancelCameraFreeze()
			end
		end

		return newFocus
	end

	------------------------

	---- Input Events ----
	local startPos = nil
	local lastPos = nil
	local panBeginLook = nil
	local lastTapTime = nil

	local fingerTouches = {}
	local NumUnsunkTouches = 0
	
	local inputStartPositions = {}
	local inputStartTimes = {}

	local StartingDiff = nil
	local pinchBeginZoom = nil

	this.ZoomEnabled = true
	this.PanEnabled = true
	this.KeyPanEnabled = true

	local function OnTouchBegan(input, processed)
		--If isDynamicThumbstickEnabled, then only process TouchBegan event if it starts in GestureArea
		
		local dtFrame = getDynamicThumbstickFrame()
		if (not touchWorkspaceEventEnabled and not isDynamicThumbstickEnabled) or positionIntersectsGuiObject(input.Position, GestureArea) and (not dtFrame or not positionIntersectsGuiObject(input.Position, dtFrame)) then
			fingerTouches[input] = processed
			if not processed then
				inputStartPositions[input] = input.Position
				inputStartTimes[input] = tick()
				NumUnsunkTouches = NumUnsunkTouches + 1
			end
		end
	end

	local function OnTouchChanged(input, processed)
		
		if fingerTouches[input] == nil then
			if isDynamicThumbstickEnabled then
				return
			end
			fingerTouches[input] = processed
			if not processed then
				NumUnsunkTouches = NumUnsunkTouches + 1
			end
		end

		if NumUnsunkTouches == 1 then
			if fingerTouches[input] == false then
				panBeginLook = panBeginLook or this:GetCameraLook()
				startPos = startPos or input.Position
				lastPos = lastPos or startPos
				this.UserPanningTheCamera = true

				local delta = input.Position - lastPos		
				
				delta = Vector2.new(delta.X, delta.Y * GameSettings:GetCameraYInvertValue())
				
				if this.PanEnabled then
					local desiredXYVector = this:ScreenTranslationToAngle(delta) * TOUCH_SENSITIVTY
					this.RotateInput = this.RotateInput + desiredXYVector
				end

				lastPos = input.Position
			end
		else
			panBeginLook = nil
			startPos = nil
			lastPos = nil
			this.UserPanningTheCamera = false
		end
		if NumUnsunkTouches == 2 then
			local unsunkTouches = {}
			for touch, wasSunk in pairs(fingerTouches) do
				if not wasSunk then
					table.insert(unsunkTouches, touch)
				end
			end
			if #unsunkTouches == 2 then
				local difference = (unsunkTouches[1].Position - unsunkTouches[2].Position).magnitude
				if StartingDiff and pinchBeginZoom then
					local scale = difference / math_max(0.01, StartingDiff)
					local clampedScale = clamp(0.1, 10, scale)
					if this.ZoomEnabled then
						this:ZoomCamera(pinchBeginZoom / clampedScale)
					end
				else
					StartingDiff = difference
					pinchBeginZoom = this:GetCameraActualZoom()
				end
			end
		else
			StartingDiff = nil
			pinchBeginZoom = nil
		end
	end
	
	local function calcLookBehindRotateInput(torso)
		if torso then
			local newDesiredLook = (torso.CFrame.lookVector - Vector3.new(0, math.sin(math.rad(DEFAULT_CAMERA_ANGLE), 0))).unit
			local horizontalShift = findAngleBetweenXZVectors(newDesiredLook, this:GetCameraLook())
			local vertShift = math.asin(this:GetCameraLook().y) - math.asin(newDesiredLook.y)
			if not IsFinite(horizontalShift) then
				horizontalShift = 0
			end
			if not IsFinite(vertShift) then
				vertShift = 0
			end
			
			return Vector2.new(horizontalShift, vertShift)
		end
		return nil
	end

	local OnTouchTap = nil
	if not touchWorkspaceEventEnabled then
		OnTouchTap = function(position)
			if isDynamicThumbstickEnabled and not IsAToolEquipped then
				if lastTapTime and tick() - lastTapTime < MAX_TIME_FOR_DOUBLE_TAP then
					local tween = {
						from = this:GetCameraZoom(),
						to = DefaultZoom,
						start = tick(),
						duration = 0.2,
						func = function(from, to, alpha)
							this:ZoomCamera(from + (to - from)*alpha)
							return to
						end
					}
					tweens["Zoom"] = tween
				else
					local humanoid = this:GetHumanoid()
					if humanoid then
						local player = PlayersService.LocalPlayer
						if player and player.Character then
							if humanoid and humanoid.Torso then
								local tween = {
									from = this.RotateInput,
									to = calcLookBehindRotateInput(humanoid.Torso),
									start = tick(),
									duration = 0.2,
									func = function(from, to, alpha)
										to = calcLookBehindRotateInput(humanoid.Torso)
										if to then
											this.RotateInput = from + (to - from)*alpha
										end
										return to
									end
								}
								tweens["Rotate"] = tween
								
								-- reset old camera info so follow cam doesn't rotate us
								this.LastCameraTransform = nil
							end
						end
					end
				end
				lastTapTime = tick()
			end
		end
	end
	
	local function IsTouchTap(input)
		-- We can't make the assumption that the input exists in the inputStartPositions because we may have switched from a different camera type.
		if inputStartPositions[input] then
			local posDelta = (inputStartPositions[input] - input.Position).magnitude
			if posDelta < MAX_TAP_POS_DELTA then
				local timeDelta = inputStartTimes[input] - tick()
				if timeDelta < MAX_TAP_TIME_DELTA then
					return true
				end
			end
		end
		return false
	end
	
	
	local function OnTouchEnded(input, processed)
		if fingerTouches[input] == false then
			if NumUnsunkTouches == 1 then
				panBeginLook = nil
				startPos = nil
				lastPos = nil
				this.UserPanningTheCamera = false
				if not touchWorkspaceEventEnabled and IsTouchTap(input) then
					OnTouchTap(input.Position)
				end
			elseif NumUnsunkTouches == 2 then
				StartingDiff = nil
				pinchBeginZoom = nil
			end
		end

		if fingerTouches[input] ~= nil and fingerTouches[input] == false then
			NumUnsunkTouches = NumUnsunkTouches - 1
		end
		fingerTouches[input] = nil
		inputStartPositions[input] = nil
		inputStartTimes[input] = nil
	end

	local function OnMousePanButtonPressed(input, processed)
		if processed then return end
		this:UpdateMouseBehavior()
		panBeginLook = panBeginLook or this:GetCameraLook()
		startPos = startPos or input.Position
		lastPos = lastPos or startPos
		this.UserPanningTheCamera = true
	end

	local function OnMousePanButtonReleased(input, processed)
		this:UpdateMouseBehavior()
		if not (isRightMouseDown or isMiddleMouseDown) then
			panBeginLook = nil
			startPos = nil
			lastPos = nil
			this.UserPanningTheCamera = false
		end
	end

	local function OnMouse2Down(input, processed)
		if processed then return end

		isRightMouseDown = true
		OnMousePanButtonPressed(input, processed)
	end

	local function OnMouse2Up(input, processed)
		isRightMouseDown = false
		OnMousePanButtonReleased(input, processed)
	end

	local function OnMouse3Down(input, processed)
		if processed then return end

		isMiddleMouseDown = true
		OnMousePanButtonPressed(input, processed)
	end

	local function OnMouse3Up(input, processed)
		isMiddleMouseDown = false
		OnMousePanButtonReleased(input, processed)
	end

	local function OnMouseMoved(input, processed)
		if not hasGameLoaded and VRService.VREnabled then
			return
		end
		
		local inputDelta = input.Delta
		inputDelta = Vector2.new(inputDelta.X, inputDelta.Y * GameSettings:GetCameraYInvertValue())
		
		if startPos and lastPos and panBeginLook then
			local currPos = lastPos + input.Delta
			local totalTrans = currPos - startPos
			if this.PanEnabled then
				local desiredXYVector = this:MouseTranslationToAngle(inputDelta) * MOUSE_SENSITIVITY
				this.RotateInput = this.RotateInput + desiredXYVector
			end
			lastPos = currPos
		elseif this:IsInFirstPerson() or this:GetShiftLock() then
			if this.PanEnabled then
				local desiredXYVector = this:MouseTranslationToAngle(inputDelta) * MOUSE_SENSITIVITY
				this.RotateInput = this.RotateInput + desiredXYVector
			end
		end
	end

	local function OnMouseWheel(input, processed)
		if not hasGameLoaded and VRService.VREnabled then
			return
		end
		if not processed then
			if this.ZoomEnabled then
                this:ZoomCameraBy(clamp(-1, 1, -input.Position.Z) * 1.4)
			end
		end
	end

	local function round(num)
		return math_floor(num + 0.5)
	end

	local eight2Pi = math_pi / 4

	local function rotateVectorByAngleAndRound(camLook, rotateAngle, roundAmount)
		if camLook ~= ZERO_VECTOR3 then
			camLook = camLook.unit
			local currAngle = math_atan2(camLook.z, camLook.x)
			local newAngle = round((math_atan2(camLook.z, camLook.x) + rotateAngle) / roundAmount) * roundAmount
			return newAngle - currAngle
		end
		return 0
	end

	local function OnKeyDown(input, processed)
		if not hasGameLoaded and VRService.VREnabled then
			return
		end
		if processed then return end
		if this.ZoomEnabled then
			if input.KeyCode == Enum.KeyCode.I then
				this:ZoomCameraBy(-5)
			elseif input.KeyCode == Enum.KeyCode.O then
				this:ZoomCameraBy(5)
			end
		end
		if panBeginLook == nil and this.KeyPanEnabled then
			if input.KeyCode == Enum.KeyCode.Left then
				this.TurningLeft = true
			elseif input.KeyCode == Enum.KeyCode.Right then
				this.TurningRight = true
			elseif input.KeyCode == Enum.KeyCode.Comma then
				local angle = rotateVectorByAngleAndRound(this:GetCameraLook() * Vector3.new(1,0,1), -eight2Pi * (3/4), eight2Pi)
				if angle ~= 0 then
					this.RotateInput = this.RotateInput + Vector2.new(angle, 0)
					this.LastUserPanCamera = tick()
					this.LastCameraTransform = nil
				end
			elseif input.KeyCode == Enum.KeyCode.Period then
				local angle = rotateVectorByAngleAndRound(this:GetCameraLook() * Vector3.new(1,0,1), eight2Pi * (3/4), eight2Pi)
				if angle ~= 0 then
					this.RotateInput = this.RotateInput + Vector2.new(angle, 0)
					this.LastUserPanCamera = tick()
					this.LastCameraTransform = nil
				end
			elseif input.KeyCode == Enum.KeyCode.PageUp then
			--elseif input.KeyCode == Enum.KeyCode.Home then
				this.RotateInput = this.RotateInput + Vector2.new(0,math.rad(15))
				this.LastCameraTransform = nil
			elseif input.KeyCode == Enum.KeyCode.PageDown then
			--elseif input.KeyCode == Enum.KeyCode.End then
				this.RotateInput = this.RotateInput + Vector2.new(0,math.rad(-15))
				this.LastCameraTransform = nil
			end
		end
	end

	local function OnKeyUp(input, processed)
		if input.KeyCode == Enum.KeyCode.Left then
			this.TurningLeft = false
		elseif input.KeyCode == Enum.KeyCode.Right then
			this.TurningRight = false
		end
	end

	local lastThumbstickRotate = nil
	local numOfSeconds = 0.7
	local currentSpeed = 0
	local maxSpeed = 6
	local vrMaxSpeed = 4
	local lastThumbstickPos = Vector2.new(0,0)
	local ySensitivity = 0.65
	local lastVelocity = nil

	-- K is a tunable parameter that changes the shape of the S-curve
	-- the larger K is the more straight/linear the curve gets
	local k = 0.35
	local lowerK = 0.8
	local function SCurveTranform(t)
		t = clamp(-1,1,t)
		if t >= 0 then
			return (k*t) / (k - t + 1)
		end
		return -((lowerK*-t) / (lowerK + t + 1))
	end

	-- DEADZONE
	local DEADZONE = 0.1
	local function toSCurveSpace(t)
		return (1 + DEADZONE) * (2*math.abs(t) - 1) - DEADZONE
	end

	local function fromSCurveSpace(t)
		return t/2 + 0.5
	end

	local function gamepadLinearToCurve(thumbstickPosition)
		local function onAxis(axisValue)
			local sign = 1
			if axisValue < 0 then
				sign = -1
			end
			local point = fromSCurveSpace(SCurveTranform(toSCurveSpace(math.abs(axisValue))))
			point = point * sign
			return clamp(-1, 1, point)
		end
		return Vector2_new(onAxis(thumbstickPosition.x), onAxis(thumbstickPosition.y))
	end

	function this:UpdateGamepad()
		local gamepadPan = this.GamepadPanningCamera
		if gamepadPan and (hasGameLoaded or not VRService.VREnabled) then
			gamepadPan = gamepadLinearToCurve(gamepadPan)
			local currentTime = tick()
			if gamepadPan.X ~= 0 or gamepadPan.Y ~= 0 then
				this.userPanningTheCamera = true
			elseif gamepadPan == ZERO_VECTOR2 then
				lastThumbstickRotate = nil
				if lastThumbstickPos == ZERO_VECTOR2 then
					currentSpeed = 0
				end
			end

			local finalConstant = 0

			if lastThumbstickRotate then
				if VRService.VREnabled then
					currentSpeed = vrMaxSpeed
				else
					local elapsedTime = (currentTime - lastThumbstickRotate) * 10
					currentSpeed = currentSpeed + (maxSpeed * ((elapsedTime*elapsedTime)/numOfSeconds))

					if currentSpeed > maxSpeed then currentSpeed = maxSpeed end

					if lastVelocity then
						local velocity = (gamepadPan - lastThumbstickPos)/(currentTime - lastThumbstickRotate)
						local velocityDeltaMag = (velocity - lastVelocity).magnitude

						if velocityDeltaMag > 12 then
							currentSpeed = currentSpeed * (20/velocityDeltaMag)
							if currentSpeed > maxSpeed then currentSpeed = maxSpeed end
						end
					end
				end

				local success, gamepadCameraSensitivity = pcall(function() return GameSettings.GamepadCameraSensitivity end)
				finalConstant = success and (gamepadCameraSensitivity * currentSpeed) or currentSpeed
				lastVelocity = (gamepadPan - lastThumbstickPos)/(currentTime - lastThumbstickRotate)
			end

			lastThumbstickPos = gamepadPan
			lastThumbstickRotate = currentTime

			return Vector2_new( gamepadPan.X * finalConstant, gamepadPan.Y * finalConstant * ySensitivity * GameSettings:GetCameraYInvertValue())
		end

		return ZERO_VECTOR2
	end

	local InputBeganConn, InputChangedConn, InputEndedConn, MenuOpenedConn, ShiftLockToggleConn, GamepadConnectedConn, GamepadDisconnectedConn, TouchActivateConn = nil, nil, nil, nil, nil, nil, nil, nil

	function this:DisconnectInputEvents()
		if InputBeganConn then
			InputBeganConn:disconnect()
			InputBeganConn = nil
		end
		if InputChangedConn then
			InputChangedConn:disconnect()
			InputChangedConn = nil
		end
		if InputEndedConn then
			InputEndedConn:disconnect()
			InputEndedConn = nil
		end
		if MenuOpenedConn then
			MenuOpenedConn:disconnect()
			MenuOpenedConn = nil
		end
		if ShiftLockToggleConn then
			ShiftLockToggleConn:disconnect()
			ShiftLockToggleConn = nil
		end
		if GamepadConnectedConn then
			GamepadConnectedConn:disconnect()
			GamepadConnectedConn = nil
		end
		if GamepadDisconnectedConn then
			GamepadDisconnectedConn:disconnect()
			GamepadDisconnectedConn = nil
		end
		if subjectStateChangedConn then
			subjectStateChangedConn:disconnect()
			subjectStateChangedConn = nil
		end
		if workspaceChangedConn then
			workspaceChangedConn:disconnect()
			workspaceChangedConn = nil
		end
		if TouchActivateConn then
			TouchActivateConn:disconnect()
			TouchActivateConn = nil
		end

		this.TurningLeft = false
		this.TurningRight = false
		this.LastCameraTransform = nil
		self.LastSubjectCFrame = nil
		this.UserPanningTheCamera = false
		this.RotateInput = Vector2.new()
		this.GamepadPanningCamera = Vector2.new(0,0)

		-- Reset input states
		startPos = nil
		lastPos = nil
		panBeginLook = nil
		isRightMouseDown = false
		isMiddleMouseDown = false

		fingerTouches = {}
		NumUnsunkTouches = 0

		StartingDiff = nil
		pinchBeginZoom = nil

		-- Unlock mouse for example if right mouse button was being held down
		if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
			UserInputService.MouseBehavior = Enum.MouseBehavior.Default
		end
	end

	function this:ResetInputStates()
		isRightMouseDown = false
		isMiddleMouseDown = false
		OnMousePanButtonReleased() -- this function doesn't seem to actually need parameters

		if UserInputService.TouchEnabled then
			--[[menu opening was causing serious touch issues
			this should disable all active touch events if
			they're active when menu opens.]]
			for inputObject, value in pairs(fingerTouches) do
				fingerTouches[inputObject] = nil
			end
			panBeginLook = nil
			startPos = nil
			lastPos = nil
			this.UserPanningTheCamera = false
			StartingDiff = nil
			pinchBeginZoom = nil
			NumUnsunkTouches = 0
		end
	end

	function this.getGamepadPan(name, state, input)
		if state == Enum.UserInputState.Cancel then
			this.GamepadPanningCamera = ZERO_VECTOR2
			return
		end		

		if input.UserInputType == this.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
			local inputVector = Vector2.new(input.Position.X, -input.Position.Y)
			if inputVector.magnitude > THUMBSTICK_DEADZONE then
				this.GamepadPanningCamera = Vector2_new(input.Position.X, -input.Position.Y)
			else
				this.GamepadPanningCamera = ZERO_VECTOR2
			end
		end
	end

	function this.doGamepadZoom(name, state, input)
		if input.UserInputType == this.activeGamepad and input.KeyCode == Enum.KeyCode.ButtonR3 and state == Enum.UserInputState.Begin then
			if this.ZoomEnabled then
				if this:GetCameraZoom() > 0.5 then
					this:ZoomCamera(0)
				else
					this:ZoomCamera(10)
				end
			end
		end
	end
	
	function this:BindGamepadInputActions()
		ContextActionService:BindAction("RootCamGamepadPan", this.getGamepadPan, false, Enum.KeyCode.Thumbstick2)
		ContextActionService:BindAction("RootCamGamepadZoom", this.doGamepadZoom, false, Enum.KeyCode.ButtonR3)
	end

	function this:ConnectInputEvents()	
		InputBeganConn = UserInputService.InputBegan:connect(function(input, processed)
			if input.UserInputType == Enum.UserInputType.Touch then
				OnTouchBegan(input, processed)
			elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
				OnMouse2Down(input, processed)
			elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
				OnMouse3Down(input, processed)
			end
			-- Keyboard
			if input.UserInputType == Enum.UserInputType.Keyboard then
				OnKeyDown(input, processed)
			end
		end)

		InputChangedConn = UserInputService.InputChanged:connect(function(input, processed)
			if input.UserInputType == Enum.UserInputType.Touch then
				OnTouchChanged(input, processed)
			elseif input.UserInputType == Enum.UserInputType.MouseMovement then
				OnMouseMoved(input, processed)
			elseif input.UserInputType == Enum.UserInputType.MouseWheel then
				OnMouseWheel(input, processed)
			end
		end)

		InputEndedConn = UserInputService.InputEnded:connect(function(input, processed)
			if input.UserInputType == Enum.UserInputType.Touch then
				OnTouchEnded(input, processed)
			elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
				OnMouse2Up(input, processed)
			elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
				OnMouse3Up(input, processed)
			end
			-- Keyboard
			if input.UserInputType == Enum.UserInputType.Keyboard then
				OnKeyUp(input, processed)
			end
		end)

		MenuOpenedConn = GuiService.MenuOpened:connect(function()
			this:ResetInputStates()
		end)

		workspaceChangedConn = workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(onCurrentCameraChanged)
		if workspace.CurrentCamera then
			onCurrentCameraChanged()
		end

		ShiftLockToggleConn = ShiftLockController.OnShiftLockToggled.Event:connect(function()
			this:UpdateMouseBehavior()
		end)

		this.RotateInput = Vector2.new()

		this.activeGamepad = nil
		local function assignActivateGamepad()
			local connectedGamepads = UserInputService:GetConnectedGamepads()
			if #connectedGamepads > 0 then
				for i = 1, #connectedGamepads do
					if this.activeGamepad == nil then
						this.activeGamepad = connectedGamepads[i]
					elseif connectedGamepads[i].Value < this.activeGamepad.Value then
						this.activeGamepad = connectedGamepads[i]
					end
				end
			end

			if this.activeGamepad == nil then -- nothing is connected, at least set up for gamepad1
				this.activeGamepad = Enum.UserInputType.Gamepad1
			end
		end

		GamepadConnectedConn = UserInputService.GamepadDisconnected:connect(function(gamepadEnum)
			if this.activeGamepad ~= gamepadEnum then return end
			this.activeGamepad = nil
			assignActivateGamepad()
		end)

		GamepadDisconnectedConn = UserInputService.GamepadConnected:connect(function(gamepadEnum)
			if this.activeGamepad == nil then
				assignActivateGamepad()
			end
		end)

		self:BindGamepadInputActions()
		
		assignActivateGamepad()

		-- set mouse behavior
		self:UpdateMouseBehavior()
	end
	
	--Process tweens related to tap-to-recenter and double-tap-to-zoom
	--Needs to be called from specific cameras on each update
	function this:ProcessTweens()
		for name, tween in pairs(tweens) do
			local alpha = math.min(1.0, (tick() - tween.start)/tween.duration)
			tween.to = tween.func(tween.from, tween.to, alpha)
			if math.abs(1 - alpha) < 0.0001 then
				tweens[name] = nil
			end
		end
	end

	function this:SetEnabled(newState)
		if newState ~= self.Enabled then
			self.Enabled = newState
			if self.Enabled then
				self:ConnectInputEvents()
				self.cframe = workspace.CurrentCamera.CFrame
			else
				self:DisconnectInputEvents()
			end
		end
	end

	local function OnPlayerAdded(player)
		player.Changed:connect(function(prop)
			if this.Enabled then
				if prop == "CameraMode" or prop == "CameraMaxZoomDistance" or prop == "CameraMinZoomDistance" then
					 this:ZoomCameraFixedBy(0)
				end
			end
		end)
		
		local function OnCharacterAdded(newCharacter)

			local humanoid = findPlayerHumanoid(player)
			local start = tick()
			while tick() - start < 0.3 and (humanoid == nil or humanoid.Torso == nil) do
				wait()
				humanoid = findPlayerHumanoid(player)
			end

			if humanoid and humanoid.Torso and player.Character == newCharacter then
				local newDesiredLook = (humanoid.Torso.CFrame.lookVector - Vector3.new(0, math.sin(math.rad(DEFAULT_CAMERA_ANGLE)), 0)).unit
				local horizontalShift = findAngleBetweenXZVectors(newDesiredLook, this:GetCameraLook())
				local vertShift = math.asin(this:GetCameraLook().y) - math.asin(newDesiredLook.y)
				if not IsFinite(horizontalShift) then
					horizontalShift = 0
				end
				if not IsFinite(vertShift) then
					vertShift = 0
				end
				this.RotateInput = Vector2.new(horizontalShift, vertShift)
				
				-- reset old camera info so follow cam doesn't rotate us
				this.LastCameraTransform = nil
			end
			
			-- Need to wait for camera cframe to update before we zoom in
			-- Not waiting will force camera to original cframe
			wait()
			this:ZoomCamera(this.DefaultZoom)
		end

		player.CharacterAdded:connect(function(character)
			if this.Enabled or SetCameraOnSpawn then
				OnCharacterAdded(character)
				SetCameraOnSpawn = false
			end
		end)
		if player.Character then
			spawn(function() OnCharacterAdded(player.Character) end)
		end
	end
	if PlayersService.LocalPlayer then
		OnPlayerAdded(PlayersService.LocalPlayer)
	end
	PlayersService.ChildAdded:connect(function(child)
		if child and PlayersService.LocalPlayer == child then
			OnPlayerAdded(PlayersService.LocalPlayer)
		end
	end)

	local function OnGameLoaded()
		hasGameLoaded = true
	end

	spawn(function()
		if game:IsLoaded() then
			OnGameLoaded()
		else
			game.Loaded:wait()
			OnGameLoaded()
		end
	end)
	
	local function OnDynamicThumbstickEnabled()
		if UserInputService.TouchEnabled then
			isDynamicThumbstickEnabled = true
		end
	end
	
	local function OnDynamicThumbstickDisabled()
		isDynamicThumbstickEnabled = false
	end
	
	local function OnGameSettingsTouchMovementModeChanged()
		if LocalPlayer.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice then
			if GameSettings.TouchMovementMode.Name == "DynamicThumbstick" then
				OnDynamicThumbstickEnabled()
			else
				OnDynamicThumbstickDisabled()
			end
		end
	end
	
	local function OnDevTouchMovementModeChanged()
		if LocalPlayer.DevTouchMovementMode.Name == "DynamicThumbstick" then
			OnDynamicThumbstickEnabled()
		else
			OnGameSettingsTouchMovementModeChanged()
		end
	end
	
	if PlayersService.LocalPlayer then
		PlayersService.LocalPlayer.Changed:Connect(function(prop)
			if prop == "DevTouchMovementMode" then
				OnDevTouchMovementModeChanged()
			end
		end)
		OnDevTouchMovementModeChanged()
	end
	
	GameSettings.Changed:Connect(function(prop)
		if prop == "TouchMovementMode" then
			OnGameSettingsTouchMovementModeChanged()
		end
	end)
	OnGameSettingsTouchMovementModeChanged()
	GameSettings:SetCameraYInvertVisible()
	pcall(function() GameSettings:SetGamepadCameraSensitivityVisible() end)
	
	return this
end

return CreateCamera
