local function l(l)if UserInputService.TouchEnabled then if PlayerGui then local l=Instance.new("\083\099\114\101\101\110\071\117\105")l.Name="\071\101\115\116\117\114\101\065\114\101\097"l.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=l end for l,b in ipairs(LocalPlayer.Character:GetChildren())do if b:IsA("\084\111\111\108")then IsAToolEquipped=true end end l.ChildAdded:Connect(function(l)if l:IsA("\084\111\111\108")then IsAToolEquipped=true end end)l.ChildRemoved:Connect(function(l)if l:IsA("\084\111\111\108")then IsAToolEquipped=false end end)end end if LocalPlayer then if LocalPlayer.Character~=nil then l(LocalPlayer.Character)end LocalPlayer.CharacterAdded:connect(function(b)l(b)end)end local function b(l,b)if l.X<b.AbsolutePosition.X+b.AbsoluteSize.X and(l.X>b.AbsolutePosition.X and(l.Y<b.AbsolutePosition.Y+b.AbsoluteSize.Y and l.Y>b.AbsolutePosition.Y))then return true end return false end local function A(l)return l:GetRenderCFrame()end local function L()local l={}local L=R15_HEAD_OFFSET function l.GetActivateValue(l)return.7 end function l.IsPortraitMode(l)return PortraitMode end function l.GetRotateAmountValue(b,l)l=l or StarterGui:GetCore("\086\082\082\111\116\097\116\105\111\110\073\110\116\101\110\115\105\116\121")if l then if l=="\076\111\119"then return VR_LOW_INTENSITY_ROTATION elseif l=="\072\105\103\104"then return VR_HIGH_INTENSITY_ROTATION end end return ZERO_VECTOR2 end function l.GetRepeatDelayValue(b,l)l=l or StarterGui:GetCore("\086\082\082\111\116\097\116\105\111\110\073\110\116\101\110\115\105\116\121")if l then if l=="\076\111\119"then return VR_LOW_INTENSITY_REPEAT elseif l=="\072\105\103\104"then return VR_HIGH_INTENSITY_REPEAT end end return 0 end l.ShiftLock=false l.Enabled=false local K=false local a=false local Z=false l.RotateInput=ZERO_VECTOR2 l.DefaultZoom=LANDSCAPE_DEFAULT_ZOOM l.activeGamepad=nil local M={}l.lastSubject=nil l.lastSubjectPosition=Vector3.new(0,5,0)local R=0 local m={}local j=false local P=nil local s=nil local function O()if UserInputService.TouchEnabled then if P then P:Disconnect()P=nil end local l=workspace.CurrentCamera if l then local b=l.ViewportSize PortraitMode=b.X<b.Y layoutGestureArea(PortraitMode)DefaultZoom=PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM P=(l:GetPropertyChangedSignal("\086\105\101\119\112\111\114\116\083\105\122\101")):Connect(function()b=l.ViewportSize PortraitMode=b.X<b.Y layoutGestureArea(PortraitMode)DefaultZoom=PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM end)end end end s=(workspace:GetPropertyChangedSignal("\067\117\114\114\101\110\116\067\097\109\101\114\097")):Connect(O)if workspace.CurrentCamera then O()end function l.GetShiftLock(l)return ShiftLockController:IsShiftLocked()end function l.GetHumanoid(l)local b=PlayersService.LocalPlayer return findPlayerHumanoid(b)end function l.GetHumanoidRootPart(b)local A=l:GetHumanoid()return A and A.Torso end function l.GetRenderCFrame(b,l)A(l)end local f=Enum.HumanoidStateType.Dead local function t(l,b)if b==f then local b=l.Parent if b then return b:FindFirstChild("\072\101\097\100")or l.Torso else return l.Torso end else return l.Torso end end local V=Enum.HumanoidStateType.Dead function l.GetSubjectPosition(b)local K=nil local a=workspace.CurrentCamera local Z=a and a.CameraSubject if Z then if Z:IsA("\072\117\109\097\110\111\105\100")then local b=Z:GetState()if VRService.VREnabled and(b==f and Z==l.lastSubject)then K=l.lastSubjectPosition else local l=t(Z,b)if l and l:IsA("\066\097\115\101\080\097\114\116")then local a=A(l)local M=ZERO_VECTOR3 if b~=f then M=Z.RigType==Enum.HumanoidRigType.R15 and L or HEAD_OFFSET end K=a.p+a:vectorToWorldSpace(M+Z.CameraOffset)end end elseif Z:IsA("\086\101\104\105\099\108\101\083\101\097\116")then local l=A(Z)local b=SEAT_OFFSET if VRService.VREnabled then b=VR_SEAT_OFFSET end K=l.p+l:vectorToWorldSpace(b)elseif Z:IsA("\083\107\097\116\101\098\111\097\114\100\080\108\097\116\102\111\114\109")then local l=A(Z)K=l.p+SEAT_OFFSET elseif Z:IsA("\066\097\115\101\080\097\114\116")then local l=A(Z)K=l.p elseif Z:IsA("\077\111\100\101\108")then K=(Z:GetModelCFrame()).p end end l.lastSubject=Z l.lastSubjectPosition=K return K end function l.ResetCameraLook(l) end function l.GetCameraLook(l)return workspace.CurrentCamera and workspace.CurrentCamera.CoordinateFrame.lookVector or Vector3.new(0,0,1)end function l.GetCameraZoom(b)if l.currentZoom==nil then local b=PlayersService.LocalPlayer l.currentZoom=b and clamp(b.CameraMinZoomDistance,b.CameraMaxZoomDistance,l.DefaultZoom)or l.DefaultZoom end return l.currentZoom end function l.GetCameraActualZoom(l)local b=workspace.CurrentCamera if b then return(b.CoordinateFrame.p-b.Focus.p).magnitude end end function l.GetCameraHeight(b)if VRService.VREnabled and not l:IsInFirstPerson()then local b=l:GetCameraZoom()return math.sin(VR_ANGLE)*b end return 0 end function l.ViewSizeX(l)local b=1024 local A=workspace.CurrentCamera if A then b=A.ViewportSize.X end return b end function l.ViewSizeY(l)local b=768 local A=workspace.CurrentCamera if A then b=A.ViewportSize.Y end return b end local T=math.asin local g=math.atan2 local n=math.floor local U=math.max local J=math.pi local x=Vector2.new local Y=Vector3.new local I=CFrame.Angles local y=CFrame.new function l.ScreenTranslationToAngle(A,b)local L=l:ViewSizeX()local K=l:ViewSizeY()local a=b.x/L local Z=b.y/K return x(a,Z)end function l.MouseTranslationToAngle(b,l)local A=l.x/1920 local L=l.y/1200 return x(A,L)end function l.RotateVector(A,l,b)local L=y(ZERO_VECTOR3,l)local K=((I(0,-b.x,0)*L)*I(-b.y,0,0)).lookVector return K,x(b.x,b.y)end function l.RotateCamera(A,l,b)if VRService.VREnabled then local L,K=A:RotateVector(l,Vector2.new(b.x,0))return(Y(L.x,0,L.z)).unit,K else local L=T(l.y)local K=clamp(-MAX_Y+L,-MIN_Y+L,b.y)return A:RotateVector(l,x(b.x,K))end end function l.IsInFirstPerson(l)return K end function l.UpdateMouseBehavior(l)if K or l: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 a or Z then UserInputService.MouseBehavior=Enum.MouseBehavior.LockCurrentPosition else UserInputService.MouseBehavior=Enum.MouseBehavior.Default end end end function l.ZoomCamera(A,b)local L=PlayersService.LocalPlayer if L then if L.CameraMode==Enum.CameraMode.LockFirstPerson then l.currentZoom=0 else l.currentZoom=clamp(L.CameraMinZoomDistance,L.CameraMaxZoomDistance,b)end end K=A:GetCameraZoom()<2 ShiftLockController:SetIsInFirstPerson(K)A:UpdateMouseBehavior()return A:GetCameraZoom()end function l.rk4Integrator(L,l,b,A)local K=b<0 and-1 or 1 local function a(l,b)local A=K*U(1,l/3.3+.5)return A end local Z=l local M=b local R=a(Z,M)local m=Z+M*(A/2)local j=M+R*(A/2)local P=a(m,j)local s=Z+j*(A/2)local O=M+P*(A/2)local f=a(s,O)local t=Z+O*A local V=M+f*A local T=a(t,V)local g=l+(((M+2*j)+2*O)+V)*(A/6)local n=b+(((R+2*P)+2*f)+T)*(A/6)return g,n end function l.ZoomCameraBy(A,b)local L=l:GetCameraActualZoom()if L then L=A:rk4Integrator(L,b,1)A:ZoomCamera(L)end return A:GetCameraZoom()end function l.ZoomCameraFixedBy(b,l)return b:ZoomCamera(b:GetCameraZoom()+l)end function l.Update(l) end function l.ApplyVRTransform(b)if not VRService.VREnabled then return end local A=PlayersService.LocalPlayer if not(A and(A.Character and(A.Character:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")and A.Character.HumanoidRootPart:FindFirstChild("\082\111\111\116\074\111\105\110\116"))))then return end local L=workspace.CurrentCamera local K=L.CameraSubject local a=K and K:IsA("\086\101\104\105\099\108\101\083\101\097\116")if l:IsInFirstPerson()and not a then local l=VRService:GetUserCFrame(Enum.UserCFrame.Head)local b=l-l.p local L=A.Character.HumanoidRootPart.RootJoint L.C0=CFrame.new(b:vectorToObjectSpace(l.p))*CFrame.new(0,0,0,-1,0,0,0,0,1,0,1,0)else local l=A.Character.HumanoidRootPart.RootJoint l.C0=CFrame.new(0,0,0,-1,0,0,0,0,1,0,1,0)end end local H=true local u=0 function l.ShouldUseVRRotation(l)if not VRService.VREnabled then return false end if not H and tick()-u<1 then return false end local b,A=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)H=b and A~=nil u=tick()return b and(A~=nil and A~="\083\109\111\111\116\104")end function l.GetVRRotationInput(l)local b=ZERO_VECTOR2 local A=StarterGui:GetCore("\086\082\082\111\116\097\116\105\111\110\073\110\116\101\110\115\105\116\121")local L=l.GamepadPanningCamera or ZERO_VECTOR2 local K=tick()-R>=l:GetRepeatDelayValue(A)if math.abs(L.x)>=l:GetActivateValue()then if K or not m[Enum.KeyCode.Thumbstick2]then local K=1 if L.x<0 then K=-1 end b=b+l:GetRotateAmountValue(A)*K m[Enum.KeyCode.Thumbstick2]=true end elseif math.abs(L.x)<l:GetActivateValue()-.1 then m[Enum.KeyCode.Thumbstick2]=nil end if l.TurningLeft then if K or not m[Enum.KeyCode.Left]then b=b-l:GetRotateAmountValue(A)m[Enum.KeyCode.Left]=true end else m[Enum.KeyCode.Left]=nil end if l.TurningRight then if K or not m[Enum.KeyCode.Right]then b=b+l:GetRotateAmountValue(A)m[Enum.KeyCode.Right]=true end else m[Enum.KeyCode.Right]=nil end if b~=ZERO_VECTOR2 then R=tick()end return b end local E=Vector3.new(1,1,1)local c=nil local N=nil local X=false local G=nil local B=nil local Q=nil local p=nil local i=nil local function D(l)if not l then E=Vector3.new(E.x,1,E.z)end if X then N=nil X=false end end local function S(l,b)if not X then c=l N=b E=Vector3.new(E.x,0,E.z)X=true end end local function W(l)L=R15_HEAD_OFFSET*l end local function h(l)if l.Name=="\066\111\100\121\072\101\105\103\104\116\083\099\097\108\101"and l:IsA("\078\117\109\098\101\114\086\097\108\117\101")then if heightScaleChangedConn then heightScaleChangedConn:disconnect()end heightScaleChangedConn=l.Changed:connect(W)W(l.Value)end end local function o(l)if l.Name=="\066\111\100\121\072\101\105\103\104\116\083\099\097\108\101"then W(1)if heightScaleChangedConn then heightScaleChangedConn:disconnect()heightScaleChangedConn=nil end end end local function e()if G then G:disconnect()G=nil end if p then p:disconnect()p=nil end if i then i:disconnect()i=nil end if heightScaleChangedConn then heightScaleChangedConn:disconnect()heightScaleChangedConn=nil end local b=workspace.CurrentCamera and workspace.CurrentCamera.CameraSubject if N~=b then D()end if b and b:IsA("\072\117\109\097\110\111\105\100")then p=b.ChildAdded:connect(h)i=b.ChildRemoved:connect(o)for l,b in pairs(b:GetChildren())do h(b)end G=b.StateChanged:connect(function(A,L)if VRService.VREnabled and(L==Enum.HumanoidStateType.Jumping and not l:IsInFirstPerson())then S(l:GetSubjectPosition(),b)elseif L~=Enum.HumanoidStateType.Jumping and L~=Enum.HumanoidStateType.Freefall then D(true)end end)end end local function C()if B then B:disconnect()B=nil end local l=workspace.CurrentCamera if l then B=(l:GetPropertyChangedSignal("\067\097\109\101\114\097\083\117\098\106\101\099\116")):connect(e)e()end end function l.GetVRFocus(A,l,b)local L=nil local K=workspace.CurrentCamera local a=A.LastCameraFocus or l if not X then E=Vector3.new(E.x,math.min(1,E.y+.42*b),E.z)end if X and(c and c.y>a.y)then L=CFrame.new(Vector3.new(l.x,math.min(c.y,a.y+5*b),l.z))else L=CFrame.new((Vector3.new(l.x,a.y,l.z)):lerp(l,E.y))end if X then if A:IsInFirstPerson()then D()end if c and l.y<c.y-.5 then D()end end return L end local z=nil local v=nil local w=nil local q=nil local r={}local F=0 local k={}local d={}local lB=nil local bB=nil l.ZoomEnabled=true l.PanEnabled=true l.KeyPanEnabled=true local function AB(l,A)if not touchWorkspaceEventEnabled and not j or b(l.Position,GestureArea)then r[l]=A if not A then k[l]=l.Position d[l]=tick()F=F+1 end end end local function LB(b,A)if r[b]==nil then if j then return end r[b]=A if not A then F=F+1 end end if F==1 then if r[b]==false then w=w or l:GetCameraLook()z=z or b.Position v=v or z l.UserPanningTheCamera=true local A=b.Position-v A=Vector2.new(A.X,A.Y*GameSettings:GetCameraYInvertValue())if l.PanEnabled then local b=l:ScreenTranslationToAngle(A)*TOUCH_SENSITIVTY l.RotateInput=l.RotateInput+b end v=b.Position end else w=nil z=nil v=nil l.UserPanningTheCamera=false end if F==2 then local b={}for l,A in pairs(r)do if not A then table.insert(b,l)end end if#b==2 then local A=(b[1].Position-b[2].Position).magnitude if lB and bB then local b=A/U(.01,lB)local L=clamp(.1,10,b)if l.ZoomEnabled then l:ZoomCamera(bB/L)end else lB=A bB=l:GetCameraActualZoom()end end else lB=nil bB=nil end end local function KB(b)if b then local A=(b.CFrame.lookVector-Vector3.new(0,.23,0)).unit local L=findAngleBetweenXZVectors(A,l:GetCameraLook())local K=math.asin((l:GetCameraLook()).y)-math.asin(A.y)if not IsFinite(L)then L=0 end if not IsFinite(K)then K=0 end return Vector2.new(L,K)end return nil end local aB=nil if not touchWorkspaceEventEnabled then aB=function(b)if j and not IsAToolEquipped then if q and tick()-q<MAX_TIME_FOR_DOUBLE_TAP then local b={from=l:GetCameraZoom(),to=DefaultZoom,start=tick(),duration=.2,func=function(b,A,L)l:ZoomCamera(b+(A-b)*L)return A end}M.Zoom=b else local b=l:GetHumanoid()if b then local A=PlayersService.LocalPlayer if A and A.Character then if b and b.Torso then local A={from=l.RotateInput,to=KB(b.Torso),start=tick(),duration=.2;func=function(A,L,K)L=KB(b.Torso)if L then l.RotateInput=A+(L-A)*K end return L end}M.Rotate=A l.LastCameraTransform=nil end end end end q=tick()end end end local function ZB(l)if k[l]then local b=(k[l]-l.Position).magnitude if b<MAX_TAP_POS_DELTA then local b=d[l]-tick()if b<MAX_TAP_TIME_DELTA then return true end end end return false end local function MB(b,A)if r[b]==false then if F==1 then w=nil z=nil v=nil l.UserPanningTheCamera=false if not touchWorkspaceEventEnabled and ZB(b)then aB(b.Position)end elseif F==2 then lB=nil bB=nil end end if r[b]~=nil and r[b]==false then F=F-1 end r[b]=nil k[b]=nil d[b]=nil end local function RB(b,A)if A then return end l:UpdateMouseBehavior()w=w or l:GetCameraLook()z=z or b.Position v=v or z l.UserPanningTheCamera=true end local function mB(b,A)l:UpdateMouseBehavior()if not(a or Z)then w=nil z=nil v=nil l.UserPanningTheCamera=false end end local function jB(l,b)if b then return end a=true RB(l,b)end local function PB(l,b)a=false mB(l,b)end local function sB(l,b)if b then return end Z=true RB(l,b)end local function OB(l,b)Z=false mB(l,b)end local function fB(b,A)if not hasGameLoaded and VRService.VREnabled then return end local L=b.Delta L=Vector2.new(L.X,L.Y*GameSettings:GetCameraYInvertValue())if z and(v and w)then local A=v+b.Delta local K=A-z if l.PanEnabled then local b=l:MouseTranslationToAngle(L)*MOUSE_SENSITIVITY l.RotateInput=l.RotateInput+b end v=A elseif l:IsInFirstPerson()or l:GetShiftLock()then if l.PanEnabled then local b=l:MouseTranslationToAngle(L)*MOUSE_SENSITIVITY l.RotateInput=l.RotateInput+b end end end local function tB(b,A)if not hasGameLoaded and VRService.VREnabled then return end if not A then if l.ZoomEnabled then l:ZoomCameraBy(clamp(-1,1,-b.Position.Z)*1.4)end end end local function VB(l)return n(l+.5)end local TB=J/4 local function gB(l,b,A)if l~=ZERO_VECTOR3 then l=l.unit local L=g(l.z,l.x)local K=VB((g(l.z,l.x)+b)/A)*A return K-L end return 0 end local function nB(b,A)if not hasGameLoaded and VRService.VREnabled then return end if A then return end if l.ZoomEnabled then if b.KeyCode==Enum.KeyCode.I then elseif b.KeyCode==Enum.KeyCode.O then end end if w==nil and l.KeyPanEnabled then if b.KeyCode==Enum.KeyCode.Left then l.TurningLeft=true elseif b.KeyCode==Enum.KeyCode.Right then l.TurningRight=true elseif b.KeyCode==Enum.KeyCode.Comma then local b=gB(l:GetCameraLook()*Vector3.new(1,0,1),-TB*.75,TB)if b~=0 then l.RotateInput=l.RotateInput+Vector2.new(b,0)l.LastUserPanCamera=tick()l.LastCameraTransform=nil end elseif b.KeyCode==Enum.KeyCode.Period then local b=gB(l:GetCameraLook()*Vector3.new(1,0,1),TB*.75,TB)if b~=0 then l.RotateInput=l.RotateInput+Vector2.new(b,0)l.LastUserPanCamera=tick()l.LastCameraTransform=nil end elseif b.KeyCode==Enum.KeyCode.PageUp then l.RotateInput=l.RotateInput+Vector2.new(0,math.rad(15))l.LastCameraTransform=nil elseif b.KeyCode==Enum.KeyCode.PageDown then l.RotateInput=l.RotateInput+Vector2.new(0,math.rad(-15))l.LastCameraTransform=nil end end end local function UB(b,A)if b.KeyCode==Enum.KeyCode.Left then l.TurningLeft=false elseif b.KeyCode==Enum.KeyCode.Right then l.TurningRight=false end end local JB=nil local xB=.7 local YB=0 local IB=6 local yB=4 local HB=Vector2.new(0,0)local uB=.65 local EB=nil local cB=.35 local NB=.8 local function XB(l)l=clamp(-1,1,l)if l>=0 then return(cB*l)/((cB-l)+1)end return-((NB*-l)/((NB+l)+1))end local GB=.1 local function BB(l)return(1+GB)*(2*math.abs(l)-1)-GB end local function QB(l)return l/2+.5 end local function pB(l)local function b(l)local b=1 if l<0 then b=-1 end local A=QB(XB(BB(math.abs(l))))A=A*b return clamp(-1,1,A)end return x(b(l.x),b(l.y))end function l.UpdateGamepad(b)local A=l.GamepadPanningCamera if A and(hasGameLoaded or not VRService.VREnabled)then A=pB(A)local b=tick()if A.X~=0 or A.Y~=0 then l.userPanningTheCamera=true elseif A==ZERO_VECTOR2 then JB=nil if HB==ZERO_VECTOR2 then YB=0 end end local L=0 if JB then if VRService.VREnabled then YB=yB else local l=(b-JB)*10 YB=YB+IB*((l*l)/xB)if YB>IB then YB=IB end if EB then local l=(A-HB)/(b-JB)local L=(l-EB).magnitude if L>12 then YB=YB*(20/L)if YB>IB then YB=IB end end end end local l,K=pcall(function()return GameSettings.GamepadCameraSensitivity end)L=l and K*YB or YB EB=(A-HB)/(b-JB)end HB=A JB=b return x(A.X*L,((A.Y*L)*uB)*GameSettings:GetCameraYInvertValue())end return ZERO_VECTOR2 end local iB,DB,SB,WB,hB,oB,eB,CB=nil,nil,nil,nil,nil,nil,nil,nil function l.DisconnectInputEvents(b)if iB then iB:disconnect()iB=nil end if DB then DB:disconnect()DB=nil end if SB then SB:disconnect()SB=nil end if WB then WB:disconnect()WB=nil end if hB then hB:disconnect()hB=nil end if oB then oB:disconnect()oB=nil end if eB then eB:disconnect()eB=nil end if G then G:disconnect()G=nil end if Q then Q:disconnect()Q=nil end if CB then CB:disconnect()CB=nil end l.TurningLeft=false l.TurningRight=false l.LastCameraTransform=nil b.LastSubjectCFrame=nil l.UserPanningTheCamera=false l.RotateInput=Vector2.new()l.GamepadPanningCamera=Vector2.new(0,0)z=nil v=nil w=nil a=false Z=false r={}F=0 lB=nil bB=nil if UserInputService.MouseBehavior~=Enum.MouseBehavior.LockCenter then UserInputService.MouseBehavior=Enum.MouseBehavior.Default end end function l.ResetInputStates(b)a=false Z=false mB()if UserInputService.TouchEnabled then for l,b in pairs(r)do r[l]=nil end w=nil z=nil v=nil l.UserPanningTheCamera=false lB=nil bB=nil F=0 end end function l.getGamepadPan(b,A,L)if L.UserInputType==l.activeGamepad and L.KeyCode==Enum.KeyCode.Thumbstick2 then if A==Enum.UserInputState.Cancel then l.GamepadPanningCamera=ZERO_VECTOR2 return end local b=Vector2.new(L.Position.X,-L.Position.Y)if b.magnitude>THUMBSTICK_DEADZONE then l.GamepadPanningCamera=x(L.Position.X,-L.Position.Y)else l.GamepadPanningCamera=ZERO_VECTOR2 end end end function l.doGamepadZoom(b,A,L)if L.UserInputType==l.activeGamepad and(L.KeyCode==Enum.KeyCode.ButtonR3 and A==Enum.UserInputState.Begin)then if l.ZoomEnabled then if l:GetCameraZoom()>.5 then l:ZoomCamera(0)else l:ZoomCamera(10)end end end end function l.BindGamepadInputActions(b)ContextActionService:BindAction("\082\111\111\116\067\097\109\071\097\109\101\112\097\100\080\097\110",l.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",l.doGamepadZoom,false,Enum.KeyCode.ButtonR3)end function l.ConnectInputEvents(A)iB=UserInputService.InputBegan:connect(function(l,b)if l.UserInputType==Enum.UserInputType.Touch then AB(l,b)elseif l.UserInputType==Enum.UserInputType.MouseButton2 then jB(l,b)elseif l.UserInputType==Enum.UserInputType.MouseButton3 then sB(l,b)end if l.UserInputType==Enum.UserInputType.Keyboard then nB(l,b)end end)DB=UserInputService.InputChanged:connect(function(l,b)if l.UserInputType==Enum.UserInputType.Touch then LB(l,b)elseif l.UserInputType==Enum.UserInputType.MouseMovement then fB(l,b)elseif l.UserInputType==Enum.UserInputType.MouseWheel then tB(l,b)end end)SB=UserInputService.InputEnded:connect(function(l,b)if l.UserInputType==Enum.UserInputType.Touch then MB(l,b)elseif l.UserInputType==Enum.UserInputType.MouseButton2 then PB(l,b)elseif l.UserInputType==Enum.UserInputType.MouseButton3 then OB(l,b)end if l.UserInputType==Enum.UserInputType.Keyboard then UB(l,b)end end)if touchWorkspaceEventEnabled then CB=UserInputService.TouchTapInWorld:connect(function(A,L)if j and(not L and(not IsAToolEquipped and b(A,GestureArea)))then if q and tick()-q<MAX_TIME_FOR_DOUBLE_TAP then local b={from=l:GetCameraZoom();to=DefaultZoom,start=tick(),duration=.2;func=function(b,A,L)l:ZoomCamera(b+(A-b)*L)return A end}M.Zoom=b else local b=l:GetHumanoid()if b then local A=PlayersService.LocalPlayer if A and A.Character then if b and b.Torso then local A={from=l.RotateInput,to=KB(b.Torso),start=tick();duration=.2;func=function(A,L,K)L=KB(b.Torso)if L then l.RotateInput=A+(L-A)*K end return L end}M.Rotate=A l.LastCameraTransform=nil end end end end q=tick()end end)end WB=GuiService.MenuOpened:connect(function()l:ResetInputStates()end)Q=(workspace:GetPropertyChangedSignal("\067\117\114\114\101\110\116\067\097\109\101\114\097")):Connect(C)if workspace.CurrentCamera then C()end hB=ShiftLockController.OnShiftLockToggled.Event:connect(function()l:UpdateMouseBehavior()end)l.RotateInput=Vector2.new()l.activeGamepad=nil local function L()local b=UserInputService:GetConnectedGamepads()if#b>0 then for A=1,#b,1 do if l.activeGamepad==nil then l.activeGamepad=b[A]elseif b[A].Value<l.activeGamepad.Value then l.activeGamepad=b[A]end end end if l.activeGamepad==nil then l.activeGamepad=Enum.UserInputType.Gamepad1 end end oB=UserInputService.GamepadDisconnected:connect(function(b)if l.activeGamepad~=b then return end l.activeGamepad=nil L()end)eB=UserInputService.GamepadConnected:connect(function(b)if l.activeGamepad==nil then L()end end)A:BindGamepadInputActions()L()A:UpdateMouseBehavior()end function l.ProcessTweens(l)for l,b in pairs(M)do local A=math.min(1.0,(tick()-b.start)/b.duration)b.to=b.func(b.from,b.to,A)if math.abs(1-A)<.0001 then M[l]=nil end end end function l.SetEnabled(b,l)if l~=b.Enabled then b.Enabled=l if b.Enabled then b:ConnectInputEvents()b.cframe=workspace.CurrentCamera.CFrame else b:DisconnectInputEvents()end end end local function zB(b)b.Changed:connect(function(b)if l.Enabled then if b=="\067\097\109\101\114\097\077\111\100\101"or b=="\067\097\109\101\114\097\077\097\120\090\111\111\109\068\105\115\116\097\110\099\101"or b=="\067\097\109\101\114\097\077\105\110\090\111\111\109\068\105\115\116\097\110\099\101"then l:ZoomCameraFixedBy(0)end end end)local function A(A)local L=findPlayerHumanoid(b)local K=tick()while tick()-K<.3 and(L==nil or L.Torso==nil)do task.wait()L=findPlayerHumanoid(b)end if L and(L.Torso and b.Character==A)then local b=(L.Torso.CFrame.lookVector-Vector3.new(0,.23,0)).unit local A=findAngleBetweenXZVectors(b,l:GetCameraLook())local K=math.asin((l:GetCameraLook()).y)-math.asin(b.y)if not IsFinite(A)then A=0 end if not IsFinite(K)then K=0 end l.RotateInput=Vector2.new(A,K)l.LastCameraTransform=nil end task.wait()l:ZoomCamera(l.DefaultZoom)end b.CharacterAdded:connect(function(b)if l.Enabled or SetCameraOnSpawn then A(b)SetCameraOnSpawn=false end end)if b.Character then spawn(function()A(b.Character)end)end end if PlayersService.LocalPlayer then zB(PlayersService.LocalPlayer)end PlayersService.ChildAdded:connect(function(l)if l and PlayersService.LocalPlayer==l then zB(PlayersService.LocalPlayer)end end)local function vB()hasGameLoaded=true end spawn(function()if game:IsLoaded()then vB()else game.Loaded:wait()vB()end end)local function wB()if UserInputService.TouchEnabled then j=true end end local function qB()j=false end local function rB()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 wB()else qB()end end end local function FB()if LocalPlayer.DevTouchMovementMode.Name=="\068\121\110\097\109\105\099\084\104\117\109\098\115\116\105\099\107"then wB()else rB()end end if PlayersService.LocalPlayer then PlayersService.LocalPlayer.Changed:Connect(function(l)if l=="\068\101\118\084\111\117\099\104\077\111\118\101\109\101\110\116\077\111\100\101"then FB()end end)FB()end GameSettings.Changed:Connect(function(l)if l=="\084\111\117\099\104\077\111\118\101\109\101\110\116\077\111\100\101"then rB()end end)rB()GameSettings:SetCameraYInvertVisible()pcall(function()GameSettings:SetGamepadCameraSensitivityVisible()end)return l end return L

local function OnCharacterAdded(character)
	if UserInputService.TouchEnabled then
		if PlayerGui then
			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
		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)
	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
	
	-- 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

						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
		if (not touchWorkspaceEventEnabled and not isDynamicThumbstickEnabled) or positionIntersectsGuiObject(input.Position, GestureArea) 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,0.23,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 input.UserInputType == this.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
			
			if state == Enum.UserInputState.Cancel then
				this.GamepadPanningCamera = ZERO_VECTOR2
				return
			end		
			
			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)

		if touchWorkspaceEventEnabled then
			TouchActivateConn = UserInputService.TouchTapInWorld:connect(function(touchPos, processed)
				if isDynamicThumbstickEnabled and not processed and not IsAToolEquipped and positionIntersectsGuiObject(touchPos, GestureArea) 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

		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
				task.wait()
				humanoid = findPlayerHumanoid(player)
			end

			if humanoid and humanoid.Torso and player.Character == newCharacter then
				local newDesiredLook = (humanoid.Torso.CFrame.lookVector - Vector3.new(0,0.23,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
			task.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
