local function m(m)if UserInputService.TouchEnabled then if PlayerGui then local m=Instance.new("\083\099\114\101\101\110\071\117\105")m.Name="\071\101\115\116\117\114\101\065\114\101\097"m.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=m end for m,W in ipairs(LocalPlayer.Character:GetChildren())do if W:IsA("\084\111\111\108")then IsAToolEquipped=true end end m.ChildAdded:Connect(function(m)if m:IsA("\084\111\111\108")then IsAToolEquipped=true end end)m.ChildRemoved:Connect(function(m)if m:IsA("\084\111\111\108")then IsAToolEquipped=false end end)end end if LocalPlayer then if LocalPlayer.Character~=nil then m(LocalPlayer.Character)end LocalPlayer.CharacterAdded:connect(function(W)m(W)end)end local function W(m,W)if m.X<W.AbsolutePosition.X+W.AbsoluteSize.X and(m.X>W.AbsolutePosition.X and(m.Y<W.AbsolutePosition.Y+W.AbsoluteSize.Y and m.Y>W.AbsolutePosition.Y))then return true end return false end local function h(m)return m:GetRenderCFrame()end local function o()local m={}local o=R15_HEAD_OFFSET function m.GetActivateValue(m)return.7 end function m.IsPortraitMode(m)return PortraitMode end function m.GetRotateAmountValue(W,m)m=m or StarterGui:GetCore("\086\082\082\111\116\097\116\105\111\110\073\110\116\101\110\115\105\116\121")if m then if m=="\076\111\119"then return VR_LOW_INTENSITY_ROTATION elseif m=="\072\105\103\104"then return VR_HIGH_INTENSITY_ROTATION end end return ZERO_VECTOR2 end function m.GetRepeatDelayValue(W,m)m=m or StarterGui:GetCore("\086\082\082\111\116\097\116\105\111\110\073\110\116\101\110\115\105\116\121")if m then if m=="\076\111\119"then return VR_LOW_INTENSITY_REPEAT elseif m=="\072\105\103\104"then return VR_HIGH_INTENSITY_REPEAT end end return 0 end m.ShiftLock=false m.Enabled=false local D=false local s=false local p=false m.RotateInput=ZERO_VECTOR2 m.DefaultZoom=LANDSCAPE_DEFAULT_ZOOM m.activeGamepad=nil local c={}m.lastSubject=nil m.lastSubjectPosition=Vector3.new(0,5,0)local Q=0 local K={}local t=false local g=nil local Y=nil local function k()if UserInputService.TouchEnabled then if g then g:Disconnect()g=nil end local m=workspace.CurrentCamera if m then local W=m.ViewportSize PortraitMode=W.X<W.Y layoutGestureArea(PortraitMode)DefaultZoom=PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM g=(m:GetPropertyChangedSignal("\086\105\101\119\112\111\114\116\083\105\122\101")):Connect(function()W=m.ViewportSize PortraitMode=W.X<W.Y layoutGestureArea(PortraitMode)DefaultZoom=PortraitMode and PORTRAIT_DEFAULT_ZOOM or LANDSCAPE_DEFAULT_ZOOM end)end end end Y=(workspace:GetPropertyChangedSignal("\067\117\114\114\101\110\116\067\097\109\101\114\097")):Connect(k)if workspace.CurrentCamera then k()end function m.GetShiftLock(m)return ShiftLockController:IsShiftLocked()end function m.GetHumanoid(m)local W=PlayersService.LocalPlayer return findPlayerHumanoid(W)end function m.GetHumanoidRootPart(W)local h=m:GetHumanoid()return h and h.Torso end function m.GetRenderCFrame(W,m)h(m)end local a=Enum.HumanoidStateType.Dead local function f(m,W)if W==a then local W=m.Parent if W then return W:FindFirstChild("\072\101\097\100")or m.Torso else return m.Torso end else return m.Torso end end local N=Enum.HumanoidStateType.Dead function m.GetSubjectPosition(W)local D=nil local s=workspace.CurrentCamera local p=s and s.CameraSubject if p then if p:IsA("\072\117\109\097\110\111\105\100")then local W=p:GetState()if VRService.VREnabled and(W==a and p==m.lastSubject)then D=m.lastSubjectPosition else local m=f(p,W)if m and m:IsA("\066\097\115\101\080\097\114\116")then local s=h(m)local c=ZERO_VECTOR3 if W~=a then c=p.RigType==Enum.HumanoidRigType.R15 and o or HEAD_OFFSET end D=s.p+s:vectorToWorldSpace(c+p.CameraOffset)end end elseif p:IsA("\086\101\104\105\099\108\101\083\101\097\116")then local m=h(p)local W=SEAT_OFFSET if VRService.VREnabled then W=VR_SEAT_OFFSET end D=m.p+m:vectorToWorldSpace(W)elseif p:IsA("\083\107\097\116\101\098\111\097\114\100\080\108\097\116\102\111\114\109")then local m=h(p)D=m.p+SEAT_OFFSET elseif p:IsA("\066\097\115\101\080\097\114\116")then local m=h(p)D=m.p elseif p:IsA("\077\111\100\101\108")then D=(p:GetModelCFrame()).p end end m.lastSubject=p m.lastSubjectPosition=D return D end function m.ResetCameraLook(m) end function m.GetCameraLook(m)return workspace.CurrentCamera and workspace.CurrentCamera.CoordinateFrame.lookVector or Vector3.new(0,0,1)end function m.GetCameraZoom(W)if m.currentZoom==nil then local W=PlayersService.LocalPlayer m.currentZoom=W and clamp(W.CameraMinZoomDistance,W.CameraMaxZoomDistance,m.DefaultZoom)or m.DefaultZoom end return m.currentZoom end function m.GetCameraActualZoom(m)local W=workspace.CurrentCamera if W then return(W.CoordinateFrame.p-W.Focus.p).magnitude end end function m.GetCameraHeight(W)if VRService.VREnabled and not m:IsInFirstPerson()then local W=m:GetCameraZoom()return math.sin(VR_ANGLE)*W end return 0 end function m.ViewSizeX(m)local W=1024 local h=workspace.CurrentCamera if h then W=h.ViewportSize.X end return W end function m.ViewSizeY(m)local W=768 local h=workspace.CurrentCamera if h then W=h.ViewportSize.Y end return W end local Z=math.asin local I=math.atan2 local z=math.floor local y=math.max local X=math.pi local E=Vector2.new local u=Vector3.new local C=CFrame.Angles local A=CFrame.new function m.ScreenTranslationToAngle(h,W)local o=m:ViewSizeX()local D=m:ViewSizeY()local s=W.x/o local p=W.y/D return E(s,p)end function m.MouseTranslationToAngle(W,m)local h=m.x/1920 local o=m.y/1200 return E(h,o)end function m.RotateVector(h,m,W)local o=A(ZERO_VECTOR3,m)local D=((C(0,-W.x,0)*o)*C(-W.y,0,0)).lookVector return D,E(W.x,W.y)end function m.RotateCamera(h,m,W)if VRService.VREnabled then local o,D=h:RotateVector(m,Vector2.new(W.x,0))return(u(o.x,0,o.z)).unit,D else local o=Z(m.y)local D=clamp(-MAX_Y+o,-MIN_Y+o,W.y)return h:RotateVector(m,E(W.x,D))end end function m.IsInFirstPerson(m)return D end function m.UpdateMouseBehavior(m)if D or m: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 s or p then UserInputService.MouseBehavior=Enum.MouseBehavior.LockCurrentPosition else UserInputService.MouseBehavior=Enum.MouseBehavior.Default end end end function m.ZoomCamera(h,W)local o=PlayersService.LocalPlayer if o then if o.CameraMode==Enum.CameraMode.LockFirstPerson then m.currentZoom=0 else m.currentZoom=clamp(o.CameraMinZoomDistance,o.CameraMaxZoomDistance,W)end end D=h:GetCameraZoom()<2 ShiftLockController:SetIsInFirstPerson(D)h:UpdateMouseBehavior()return h:GetCameraZoom()end function m.rk4Integrator(o,m,W,h)local D=W<0 and-1 or 1 local function s(m,W)local h=D*y(1,m/3.3+.5)return h end local p=m local c=W local Q=s(p,c)local K=p+c*(h/2)local t=c+Q*(h/2)local g=s(K,t)local Y=p+t*(h/2)local k=c+g*(h/2)local a=s(Y,k)local f=p+k*h local N=c+a*h local Z=s(f,N)local I=m+(((c+2*t)+2*k)+N)*(h/6)local z=W+(((Q+2*g)+2*a)+Z)*(h/6)return I,z end function m.ZoomCameraBy(h,W)local o=m:GetCameraActualZoom()if o then o=h:rk4Integrator(o,W,1)h:ZoomCamera(o)end return h:GetCameraZoom()end function m.ZoomCameraFixedBy(W,m)return W:ZoomCamera(W:GetCameraZoom()+m)end function m.Update(m) end function m.ApplyVRTransform(W)if not VRService.VREnabled then return end local h=PlayersService.LocalPlayer if not(h and(h.Character and(h.Character:FindFirstChild("\072\117\109\097\110\111\105\100\082\111\111\116\080\097\114\116")and h.Character.HumanoidRootPart:FindFirstChild("\082\111\111\116\074\111\105\110\116"))))then return end local o=workspace.CurrentCamera local D=o.CameraSubject local s=D and D:IsA("\086\101\104\105\099\108\101\083\101\097\116")if m:IsInFirstPerson()and not s then local m=VRService:GetUserCFrame(Enum.UserCFrame.Head)local W=m-m.p local o=h.Character.HumanoidRootPart.RootJoint o.C0=CFrame.new(W:vectorToObjectSpace(m.p))*CFrame.new(0,0,0,-1,0,0,0,0,1,0,1,0)else local m=h.Character.HumanoidRootPart.RootJoint m.C0=CFrame.new(0,0,0,-1,0,0,0,0,1,0,1,0)end end local b=true local T=0 function m.ShouldUseVRRotation(m)if not VRService.VREnabled then return false end if not b and tick()-T<1 then return false end local W,h=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)b=W and h~=nil T=tick()return W and(h~=nil and h~="\083\109\111\111\116\104")end function m.GetVRRotationInput(m)local W=ZERO_VECTOR2 local h=StarterGui:GetCore("\086\082\082\111\116\097\116\105\111\110\073\110\116\101\110\115\105\116\121")local o=m.GamepadPanningCamera or ZERO_VECTOR2 local D=tick()-Q>=m:GetRepeatDelayValue(h)if math.abs(o.x)>=m:GetActivateValue()then if D or not K[Enum.KeyCode.Thumbstick2]then local D=1 if o.x<0 then D=-1 end W=W+m:GetRotateAmountValue(h)*D K[Enum.KeyCode.Thumbstick2]=true end elseif math.abs(o.x)<m:GetActivateValue()-.1 then K[Enum.KeyCode.Thumbstick2]=nil end if m.TurningLeft then if D or not K[Enum.KeyCode.Left]then W=W-m:GetRotateAmountValue(h)K[Enum.KeyCode.Left]=true end else K[Enum.KeyCode.Left]=nil end if m.TurningRight then if D or not K[Enum.KeyCode.Right]then W=W+m:GetRotateAmountValue(h)K[Enum.KeyCode.Right]=true end else K[Enum.KeyCode.Right]=nil end if W~=ZERO_VECTOR2 then Q=tick()end return W end local v=Vector3.new(1,1,1)local r=nil local O=nil local F=false local d=nil local B=nil local l=nil local n=nil local H=nil local function S(m)if not m then v=Vector3.new(v.x,1,v.z)end if F then O=nil F=false end end local function i(m,W)if not F then r=m O=W v=Vector3.new(v.x,0,v.z)F=true end end local function V(m)o=R15_HEAD_OFFSET*m end local function w(m)if m.Name=="\066\111\100\121\072\101\105\103\104\116\083\099\097\108\101"and m:IsA("\078\117\109\098\101\114\086\097\108\117\101")then if heightScaleChangedConn then heightScaleChangedConn:disconnect()end heightScaleChangedConn=m.Changed:connect(V)V(m.Value)end end local function q(m)if m.Name=="\066\111\100\121\072\101\105\103\104\116\083\099\097\108\101"then V(1)if heightScaleChangedConn then heightScaleChangedConn:disconnect()heightScaleChangedConn=nil end end end local function x()if d then d:disconnect()d=nil end if n then n:disconnect()n=nil end if H then H:disconnect()H=nil end if heightScaleChangedConn then heightScaleChangedConn:disconnect()heightScaleChangedConn=nil end local W=workspace.CurrentCamera and workspace.CurrentCamera.CameraSubject if O~=W then S()end if W and W:IsA("\072\117\109\097\110\111\105\100")then n=W.ChildAdded:connect(w)H=W.ChildRemoved:connect(q)for m,W in pairs(W:GetChildren())do w(W)end d=W.StateChanged:connect(function(h,o)if VRService.VREnabled and(o==Enum.HumanoidStateType.Jumping and not m:IsInFirstPerson())then i(m:GetSubjectPosition(),W)elseif o~=Enum.HumanoidStateType.Jumping and o~=Enum.HumanoidStateType.Freefall then S(true)end end)end end local function P()if B then B:disconnect()B=nil end local m=workspace.CurrentCamera if m then B=(m:GetPropertyChangedSignal("\067\097\109\101\114\097\083\117\098\106\101\099\116")):connect(x)x()end end function m.GetVRFocus(h,m,W)local o=nil local D=workspace.CurrentCamera local s=h.LastCameraFocus or m if not F then v=Vector3.new(v.x,math.min(1,v.y+.42*W),v.z)end if F and(r and r.y>s.y)then o=CFrame.new(Vector3.new(m.x,math.min(r.y,s.y+5*W),m.z))else o=CFrame.new((Vector3.new(m.x,s.y,m.z)):lerp(m,v.y))end if F then if h:IsInFirstPerson()then S()end if r and m.y<r.y-.5 then S()end end return o end local U=nil local M=nil local J=nil local R=nil local L={}local j=0 local e={}local G={}local mU=nil local WU=nil m.ZoomEnabled=true m.PanEnabled=true m.KeyPanEnabled=true local function hU(m,h)if not touchWorkspaceEventEnabled and not t or W(m.Position,GestureArea)then L[m]=h if not h then e[m]=m.Position G[m]=tick()j=j+1 end end end local function oU(W,h)if L[W]==nil then if t then return end L[W]=h if not h then j=j+1 end end if j==1 then if L[W]==false then J=J or m:GetCameraLook()U=U or W.Position M=M or U m.UserPanningTheCamera=true local h=W.Position-M h=Vector2.new(h.X,h.Y*GameSettings:GetCameraYInvertValue())if m.PanEnabled then local W=m:ScreenTranslationToAngle(h)*TOUCH_SENSITIVTY m.RotateInput=m.RotateInput+W end M=W.Position end else J=nil U=nil M=nil m.UserPanningTheCamera=false end if j==2 then local W={}for m,h in pairs(L)do if not h then table.insert(W,m)end end if#W==2 then local h=(W[1].Position-W[2].Position).magnitude if mU and WU then local W=h/y(.01,mU)local o=clamp(.1,10,W)if m.ZoomEnabled then m:ZoomCamera(WU/o)end else mU=h WU=m:GetCameraActualZoom()end end else mU=nil WU=nil end end local function DU(W)if W then local h=(W.CFrame.lookVector-Vector3.new(0,.23,0)).unit local o=findAngleBetweenXZVectors(h,m:GetCameraLook())local D=math.asin((m:GetCameraLook()).y)-math.asin(h.y)if not IsFinite(o)then o=0 end if not IsFinite(D)then D=0 end return Vector2.new(o,D)end return nil end local sU=nil if not touchWorkspaceEventEnabled then sU=function(W)if t and not IsAToolEquipped then if R and tick()-R<MAX_TIME_FOR_DOUBLE_TAP then local W={from=m:GetCameraZoom(),to=DefaultZoom,start=tick();duration=.2;func=function(W,h,o)m:ZoomCamera(W+(h-W)*o)return h end}c.Zoom=W else local W=m:GetHumanoid()if W then local h=PlayersService.LocalPlayer if h and h.Character then if W and W.Torso then local h={from=m.RotateInput;to=DU(W.Torso);start=tick(),duration=.2;func=function(h,o,D)o=DU(W.Torso)if o then m.RotateInput=h+(o-h)*D end return o end}c.Rotate=h m.LastCameraTransform=nil end end end end R=tick()end end end local function pU(m)if e[m]then local W=(e[m]-m.Position).magnitude if W<MAX_TAP_POS_DELTA then local W=G[m]-tick()if W<MAX_TAP_TIME_DELTA then return true end end end return false end local function cU(W,h)if L[W]==false then if j==1 then J=nil U=nil M=nil m.UserPanningTheCamera=false if not touchWorkspaceEventEnabled and pU(W)then sU(W.Position)end elseif j==2 then mU=nil WU=nil end end if L[W]~=nil and L[W]==false then j=j-1 end L[W]=nil e[W]=nil G[W]=nil end local function QU(W,h)if h then return end m:UpdateMouseBehavior()J=J or m:GetCameraLook()U=U or W.Position M=M or U m.UserPanningTheCamera=true end local function KU(W,h)m:UpdateMouseBehavior()if not(s or p)then J=nil U=nil M=nil m.UserPanningTheCamera=false end end local function tU(m,W)if W then return end s=true QU(m,W)end local function gU(m,W)s=false KU(m,W)end local function YU(m,W)if W then return end p=true QU(m,W)end local function kU(m,W)p=false KU(m,W)end local function aU(W,h)if not hasGameLoaded and VRService.VREnabled then return end local o=W.Delta o=Vector2.new(o.X,o.Y*GameSettings:GetCameraYInvertValue())if U and(M and J)then local h=M+W.Delta local D=h-U if m.PanEnabled then local W=m:MouseTranslationToAngle(o)*MOUSE_SENSITIVITY m.RotateInput=m.RotateInput+W end M=h elseif m:IsInFirstPerson()or m:GetShiftLock()then if m.PanEnabled then local W=m:MouseTranslationToAngle(o)*MOUSE_SENSITIVITY m.RotateInput=m.RotateInput+W end end end local function fU(W,h)if not hasGameLoaded and VRService.VREnabled then return end if not h then if m.ZoomEnabled then m:ZoomCameraBy(clamp(-1,1,-W.Position.Z)*1.4)end end end local function NU(m)return z(m+.5)end local ZU=X/4 local function IU(m,W,h)if m~=ZERO_VECTOR3 then m=m.unit local o=I(m.z,m.x)local D=NU((I(m.z,m.x)+W)/h)*h return D-o end return 0 end local function zU(W,h)if not hasGameLoaded and VRService.VREnabled then return end if h then return end if m.ZoomEnabled then if W.KeyCode==Enum.KeyCode.I then m:ZoomCameraBy(-5)elseif W.KeyCode==Enum.KeyCode.O then m:ZoomCameraBy(5)end end if J==nil and m.KeyPanEnabled then if W.KeyCode==Enum.KeyCode.Left then m.TurningLeft=true elseif W.KeyCode==Enum.KeyCode.Right then m.TurningRight=true elseif W.KeyCode==Enum.KeyCode.Comma then local W=IU(m:GetCameraLook()*Vector3.new(1,0,1),-ZU*.75,ZU)if W~=0 then m.RotateInput=m.RotateInput+Vector2.new(W,0)m.LastUserPanCamera=tick()m.LastCameraTransform=nil end elseif W.KeyCode==Enum.KeyCode.Period then local W=IU(m:GetCameraLook()*Vector3.new(1,0,1),ZU*.75,ZU)if W~=0 then m.RotateInput=m.RotateInput+Vector2.new(W,0)m.LastUserPanCamera=tick()m.LastCameraTransform=nil end elseif W.KeyCode==Enum.KeyCode.PageUp then m.RotateInput=m.RotateInput+Vector2.new(0,math.rad(15))m.LastCameraTransform=nil elseif W.KeyCode==Enum.KeyCode.PageDown then m.RotateInput=m.RotateInput+Vector2.new(0,math.rad(-15))m.LastCameraTransform=nil end end end local function yU(W,h)if W.KeyCode==Enum.KeyCode.Left then m.TurningLeft=false elseif W.KeyCode==Enum.KeyCode.Right then m.TurningRight=false end end local XU=nil local EU=.7 local uU=0 local CU=6 local AU=4 local bU=Vector2.new(0,0)local TU=.65 local vU=nil local rU=.35 local OU=.8 local function FU(m)m=clamp(-1,1,m)if m>=0 then return(rU*m)/((rU-m)+1)end return-((OU*-m)/((OU+m)+1))end local dU=.1 local function BU(m)return(1+dU)*(2*math.abs(m)-1)-dU end local function lU(m)return m/2+.5 end local function nU(m)local function W(m)local W=1 if m<0 then W=-1 end local h=lU(FU(BU(math.abs(m))))h=h*W return clamp(-1,1,h)end return E(W(m.x),W(m.y))end function m.UpdateGamepad(W)local h=m.GamepadPanningCamera if h and(hasGameLoaded or not VRService.VREnabled)then h=nU(h)local W=tick()if h.X~=0 or h.Y~=0 then m.userPanningTheCamera=true elseif h==ZERO_VECTOR2 then XU=nil if bU==ZERO_VECTOR2 then uU=0 end end local o=0 if XU then if VRService.VREnabled then uU=AU else local m=(W-XU)*10 uU=uU+CU*((m*m)/EU)if uU>CU then uU=CU end if vU then local m=(h-bU)/(W-XU)local o=(m-vU).magnitude if o>12 then uU=uU*(20/o)if uU>CU then uU=CU end end end end local m,D=pcall(function()return GameSettings.GamepadCameraSensitivity end)o=m and D*uU or uU vU=(h-bU)/(W-XU)end bU=h XU=W return E(h.X*o,((h.Y*o)*TU)*GameSettings:GetCameraYInvertValue())end return ZERO_VECTOR2 end local HU,SU,iU,VU,wU,qU,xU,PU=nil,nil,nil,nil,nil,nil,nil,nil function m.DisconnectInputEvents(W)if HU then HU:disconnect()HU=nil end if SU then SU:disconnect()SU=nil end if iU then iU:disconnect()iU=nil end if VU then VU:disconnect()VU=nil end if wU then wU:disconnect()wU=nil end if qU then qU:disconnect()qU=nil end if xU then xU:disconnect()xU=nil end if d then d:disconnect()d=nil end if l then l:disconnect()l=nil end if PU then PU:disconnect()PU=nil end m.TurningLeft=false m.TurningRight=false m.LastCameraTransform=nil W.LastSubjectCFrame=nil m.UserPanningTheCamera=false m.RotateInput=Vector2.new()m.GamepadPanningCamera=Vector2.new(0,0)U=nil M=nil J=nil s=false p=false L={}j=0 mU=nil WU=nil if UserInputService.MouseBehavior~=Enum.MouseBehavior.LockCenter then UserInputService.MouseBehavior=Enum.MouseBehavior.Default end end function m.ResetInputStates(W)s=false p=false KU()if UserInputService.TouchEnabled then for m,W in pairs(L)do L[m]=nil end J=nil U=nil M=nil m.UserPanningTheCamera=false mU=nil WU=nil j=0 end end function m.getGamepadPan(W,h,o)if o.UserInputType==m.activeGamepad and o.KeyCode==Enum.KeyCode.Thumbstick2 then if h==Enum.UserInputState.Cancel then m.GamepadPanningCamera=ZERO_VECTOR2 return end local W=Vector2.new(o.Position.X,-o.Position.Y)if W.magnitude>THUMBSTICK_DEADZONE then m.GamepadPanningCamera=E(o.Position.X,-o.Position.Y)else m.GamepadPanningCamera=ZERO_VECTOR2 end end end function m.doGamepadZoom(W,h,o)if o.UserInputType==m.activeGamepad and(o.KeyCode==Enum.KeyCode.ButtonR3 and h==Enum.UserInputState.Begin)then if m.ZoomEnabled then if m:GetCameraZoom()>.5 then m:ZoomCamera(0)else m:ZoomCamera(10)end end end end function m.BindGamepadInputActions(W)ContextActionService:BindAction("\082\111\111\116\067\097\109\071\097\109\101\112\097\100\080\097\110",m.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",m.doGamepadZoom,false,Enum.KeyCode.ButtonR3)end function m.ConnectInputEvents(h)HU=UserInputService.InputBegan:connect(function(m,W)if m.UserInputType==Enum.UserInputType.Touch then hU(m,W)elseif m.UserInputType==Enum.UserInputType.MouseButton2 then tU(m,W)elseif m.UserInputType==Enum.UserInputType.MouseButton3 then YU(m,W)end if m.UserInputType==Enum.UserInputType.Keyboard then zU(m,W)end end)SU=UserInputService.InputChanged:connect(function(m,W)if m.UserInputType==Enum.UserInputType.Touch then oU(m,W)elseif m.UserInputType==Enum.UserInputType.MouseMovement then aU(m,W)elseif m.UserInputType==Enum.UserInputType.MouseWheel then fU(m,W)end end)iU=UserInputService.InputEnded:connect(function(m,W)if m.UserInputType==Enum.UserInputType.Touch then cU(m,W)elseif m.UserInputType==Enum.UserInputType.MouseButton2 then gU(m,W)elseif m.UserInputType==Enum.UserInputType.MouseButton3 then kU(m,W)end if m.UserInputType==Enum.UserInputType.Keyboard then yU(m,W)end end)if touchWorkspaceEventEnabled then PU=UserInputService.TouchTapInWorld:connect(function(h,o)if t and(not o and(not IsAToolEquipped and W(h,GestureArea)))then if R and tick()-R<MAX_TIME_FOR_DOUBLE_TAP then local W={from=m:GetCameraZoom();to=DefaultZoom;start=tick();duration=.2;func=function(W,h,o)m:ZoomCamera(W+(h-W)*o)return h end}c.Zoom=W else local W=m:GetHumanoid()if W then local h=PlayersService.LocalPlayer if h and h.Character then if W and W.Torso then local h={from=m.RotateInput;to=DU(W.Torso);start=tick();duration=.2,func=function(h,o,D)o=DU(W.Torso)if o then m.RotateInput=h+(o-h)*D end return o end}c.Rotate=h m.LastCameraTransform=nil end end end end R=tick()end end)end VU=GuiService.MenuOpened:connect(function()m:ResetInputStates()end)l=(workspace:GetPropertyChangedSignal("\067\117\114\114\101\110\116\067\097\109\101\114\097")):Connect(P)if workspace.CurrentCamera then P()end wU=ShiftLockController.OnShiftLockToggled.Event:connect(function()m:UpdateMouseBehavior()end)m.RotateInput=Vector2.new()m.activeGamepad=nil local function o()local W=UserInputService:GetConnectedGamepads()if#W>0 then for h=1,#W,1 do if m.activeGamepad==nil then m.activeGamepad=W[h]elseif W[h].Value<m.activeGamepad.Value then m.activeGamepad=W[h]end end end if m.activeGamepad==nil then m.activeGamepad=Enum.UserInputType.Gamepad1 end end qU=UserInputService.GamepadDisconnected:connect(function(W)if m.activeGamepad~=W then return end m.activeGamepad=nil o()end)xU=UserInputService.GamepadConnected:connect(function(W)if m.activeGamepad==nil then o()end end)h:BindGamepadInputActions()o()h:UpdateMouseBehavior()end function m.ProcessTweens(m)for m,W in pairs(c)do local h=math.min(1.0,(tick()-W.start)/W.duration)W.to=W.func(W.from,W.to,h)if math.abs(1-h)<.0001 then c[m]=nil end end end function m.SetEnabled(W,m)if m~=W.Enabled then W.Enabled=m if W.Enabled then W:ConnectInputEvents()W.cframe=workspace.CurrentCamera.CFrame else W:DisconnectInputEvents()end end end local function UU(W)W.Changed:connect(function(W)if m.Enabled then if W=="\067\097\109\101\114\097\077\111\100\101"or W=="\067\097\109\101\114\097\077\097\120\090\111\111\109\068\105\115\116\097\110\099\101"or W=="\067\097\109\101\114\097\077\105\110\090\111\111\109\068\105\115\116\097\110\099\101"then m:ZoomCameraFixedBy(0)end end end)local function h(h)local o=findPlayerHumanoid(W)local D=tick()while tick()-D<.3 and(o==nil or o.Torso==nil)do wait()o=findPlayerHumanoid(W)end if o and(o.Torso and W.Character==h)then local W=(o.Torso.CFrame.lookVector-Vector3.new(0,.23,0)).unit local h=findAngleBetweenXZVectors(W,m:GetCameraLook())local D=math.asin((m:GetCameraLook()).y)-math.asin(W.y)if not IsFinite(h)then h=0 end if not IsFinite(D)then D=0 end m.RotateInput=Vector2.new(h,D)m.LastCameraTransform=nil end wait()m:ZoomCamera(m.DefaultZoom)end W.CharacterAdded:connect(function(W)if m.Enabled or SetCameraOnSpawn then h(W)SetCameraOnSpawn=false end end)if W.Character then spawn(function()h(W.Character)end)end end if PlayersService.LocalPlayer then UU(PlayersService.LocalPlayer)end PlayersService.ChildAdded:connect(function(m)if m and PlayersService.LocalPlayer==m then UU(PlayersService.LocalPlayer)end end)local function MU()hasGameLoaded=true end spawn(function()if game:IsLoaded()then MU()else game.Loaded:wait()MU()end end)local function JU()if UserInputService.TouchEnabled then t=true end end local function RU()t=false end local function LU()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 JU()else RU()end end end local function jU()if LocalPlayer.DevTouchMovementMode.Name=="\068\121\110\097\109\105\099\084\104\117\109\098\115\116\105\099\107"then JU()else LU()end end if PlayersService.LocalPlayer then PlayersService.LocalPlayer.Changed:Connect(function(m)if m=="\068\101\118\084\111\117\099\104\077\111\118\101\109\101\110\116\077\111\100\101"then jU()end end)jU()end GameSettings.Changed:Connect(function(m)if m=="\084\111\117\099\104\077\111\118\101\109\101\110\116\077\111\100\101"then LU()end end)LU()GameSettings:SetCameraYInvertVisible()pcall(function()GameSettings:SetGamepadCameraSensitivityVisible()end)return m end return o

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
				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
			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
