null
nil
-
false
CameraScript
-
ClickToMove
y then
return x
else
return y
end
end
end
Utility.ViewSizeX = ViewSizeX
local function ViewSizeY()
local camera = workspace.CurrentCamera
local x = camera and camera.ViewportSize.X or 0
local y = camera and camera.ViewportSize.Y or 0
if y == 0 then
return 768
else
if x > y then
return y
else
return x
end
end
end
Utility.ViewSizeY = ViewSizeY
local function AspectRatio()
return ViewSizeX() / ViewSizeY()
end
Utility.AspectRatio = AspectRatio
local function FindChacterAncestor(part)
if part then
local humanoid = part:FindFirstChild("Humanoid")
if humanoid then
return part, humanoid
else
return FindChacterAncestor(part.Parent)
end
end
end
Utility.FindChacterAncestor = FindChacterAncestor
local function GetUnitRay(x, y, viewWidth, viewHeight, camera)
return camera:ScreenPointToRay(x, y)
end
Utility.GetUnitRay = GetUnitRay
local RayCastIgnoreList = workspace.FindPartOnRayWithIgnoreList
local function Raycast(ray, ignoreNonCollidable, ignoreList)
local ignoreList = ignoreList or {}
local hitPart, hitPos = RayCastIgnoreList(workspace, ray, ignoreList)
if hitPart then
if ignoreNonCollidable and hitPart.CanCollide == false then
table.insert(ignoreList, hitPart)
return Raycast(ray, ignoreNonCollidable, ignoreList)
end
return hitPart, hitPos
end
return nil, nil
end
Utility.Raycast = Raycast
Utility.Round = function(num, roundToNearest)
roundToNearest = roundToNearest or 1
return math.floor((num + roundToNearest/2) / roundToNearest) * roundToNearest
end
local function AveragePoints(positions)
local avgPos = Vector2.new(0,0)
if #positions > 0 then
for i = 1, #positions do
avgPos = avgPos + positions[i]
end
avgPos = avgPos / #positions
end
return avgPos
end
Utility.AveragePoints = AveragePoints
local function FuzzyEquals(numa, numb)
return numa + 0.1 > numb and numa - 0.1 < numb
end
Utility.FuzzyEquals = FuzzyEquals
local LastInput = 0
UIS.InputBegan:connect(function(inputObject, wasSunk)
if not wasSunk then
if inputObject.UserInputType == Enum.UserInputType.Touch or
inputObject.UserInputType == Enum.UserInputType.MouseButton1 or
inputObject.UserInputType == Enum.UserInputType.MouseButton2 then
LastInput = tick()
end
end
end)
Utility.GetLastInput = function()
return LastInput
end
end
local humanoidCache = {}
local function findPlayerHumanoid(player)
local character = player and player.Character
if character then
local resultHumanoid = humanoidCache[player]
if resultHumanoid and resultHumanoid.Parent == character then
return resultHumanoid
else
humanoidCache[player] = nil -- Bust Old Cache
for _, child in pairs(character:GetChildren()) do
if child:IsA('Humanoid') then
humanoidCache[player] = child
return child
end
end
end
end
end
local function CFrameInterpolator(c0, c1) -- (CFrame from, CFrame to) -> (float theta, (float fraction -> CFrame between))
local fromAxisAngle = CFrame.fromAxisAngle
local components = CFrame.new().components
local inverse = CFrame.new().inverse
local v3 = Vector3.new
local acos = math.acos
local sqrt = math.sqrt
local invroot2 = 1 / math.sqrt(2)
-- The expanded matrix
local _, _, _, xx, yx, zx,
xy, yy, zy,
xz, yz, zz = components(inverse(c0)*c1)
-- The cos-theta of the axisAngles from
local cosTheta = (xx + yy + zz - 1)/2
-- Rotation axis
local rotationAxis = v3(yz-zy, zx-xz, xy-yx)
-- The position to tween through
local positionDelta = (c1.p - c0.p)
-- Theta
local theta;
-- Catch degenerate cases
if cosTheta >= 0.999 then
-- Case same rotation, just return an interpolator over the positions
return 0, function(t)
return c0 + positionDelta*t
end
elseif cosTheta <= -0.999 then
-- Case exactly opposite rotations, disambiguate
theta = math.pi
xx = (xx + 1) / 2
yy = (yy + 1) / 2
zz = (zz + 1) / 2
if xx > yy and xx > zz then
if xx < 0.001 then
rotationAxis = v3(0, invroot2, invroot2)
else
local x = sqrt(xx)
xy = (xy + yx) / 4
xz = (xz + zx) / 4
rotationAxis = v3(x, xy/x, xz/x)
end
elseif yy > zz then
if yy < 0.001 then
rotationAxis = v3(invroot2, 0, invroot2)
else
local y = sqrt(yy)
xy = (xy + yx) / 4
yz = (yz + zy) / 4
rotationAxis = v3(xy/y, y, yz/y)
end
else
if zz < 0.001 then
rotationAxis = v3(invroot2, invroot2, 0)
else
local z = sqrt(zz)
xz = (xz + zx) / 4
yz = (yz + zy) / 4
rotationAxis = v3(xz/z, yz/z, z)
end
end
else
-- Normal case, get theta from cosTheta
theta = acos(cosTheta)
end
-- Return the interpolator
return theta, function(t)
return c0*fromAxisAngle(rotationAxis, theta*t) + positionDelta*t
end
end
---------------------------------------------------------
local Signal = Utility.Signal
local Create = Utility.Create
--------------------------CHARACTER CONTROL-------------------------------
local function CreateController()
local this = {}
this.TorsoLookPoint = nil
function this:SetTorsoLookPoint(point)
local humanoid = findPlayerHumanoid(Player)
if humanoid then
humanoid.AutoRotate = false
end
this.TorsoLookPoint = point
self:UpdateTorso()
delay(2,
function()
-- this isnt technically correct for detecting if this is the last issue to the setTorso function
if this.TorsoLookPoint == point then
this.TorsoLookPoint = nil
if humanoid then
humanoid.AutoRotate = true
end
end
end)
end
function this:UpdateTorso(point)
if this.TorsoLookPoint then
point = this.TorsoLookPoint
else
return
end
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
if torso then
local lookVec = (point - torso.CFrame.p).unit
local squashedLookVec = Vector3.new(lookVec.X, 0, lookVec.Z).unit
torso.CFrame = CFrame.new(torso.CFrame.p, torso.CFrame.p + squashedLookVec)
end
end
return this
end
local CharacterControl = CreateController()
-----------------------------------------------------------------------
--------------------------PC AUTO JUMPER-------------------------------
local function GetCharacter()
return Player and Player.Character
end
local function GetTorso()
local humanoid = findPlayerHumanoid(Player)
return humanoid and humanoid.Torso
end
local function IsPartAHumanoid(part)
return part and part.Parent and (part.Parent:FindFirstChild('Humanoid') ~= nil)
end
local function doAutoJump()
local character = GetCharacter()
if (character == nil) then
return;
end
local humanoid = findPlayerHumanoid(Player)
if (humanoid == nil) then
return;
end
local rayLength = 1.5;
-- This is how high a ROBLOXian jumps from the mid point of his torso
local jumpHeight = 7.0;
local torso = GetTorso()
if (torso == nil) then
return;
end
local torsoCFrame = torso.CFrame;
local torsoLookVector = torsoCFrame.lookVector;
local torsoPos = torsoCFrame.p;
local torsoRay = Ray.new(torsoPos + Vector3.new(0, -torso.Size.Y/2, 0), torsoLookVector * rayLength);
local jumpRay = Ray.new(torsoPos + Vector3.new(0, jumpHeight - torso.Size.Y, 0), torsoLookVector * rayLength);
local hitPart, _ = RayCastIgnoreList(workspace, torsoRay, {character}, false)
local jumpHitPart, _ = RayCastIgnoreList(workspace, jumpRay, {character}, false)
if (hitPart and jumpHitPart == nil and hitPart.CanCollide == true) then
-- NOTE: this follow line is not in the C++ impl, but an improvement in Click to Move
if not IsPartAHumanoid(hitPart) then
humanoid.Jump = true;
end
end
end
local NO_JUMP_STATES =
{
[Enum.HumanoidStateType.FallingDown] = false;
[Enum.HumanoidStateType.Flying] = false;
[Enum.HumanoidStateType.Freefall] = false;
[Enum.HumanoidStateType.GettingUp] = false;
[Enum.HumanoidStateType.Ragdoll] = false;
[Enum.HumanoidStateType.Running] = false;
[Enum.HumanoidStateType.Seated] = false;
[Enum.HumanoidStateType.Swimming] = false;
-- Special case to detect if we are on a ladder
[Enum.HumanoidStateType.Climbing] = false;
}
local function enableAutoJump()
local humanoid = findPlayerHumanoid(Player)
local currentState = humanoid and humanoid:GetState()
if currentState then
return NO_JUMP_STATES[currentState] == nil
end
return false
end
local function getAutoJump()
return true
end
local function vec3IsZero(vec3)
return vec3.magnitude < 0.05
end
-- NOTE: This function is radically different from the engine's implementation
local function calcDesiredWalkVelocity()
-- TEMP
return Vector3.new(1,1,1)
end
local function preStepSimulatorSide(dt)
if getAutoJump() and enableAutoJump() then
local desiredWalkVelocity = calcDesiredWalkVelocity();
if (not vec3IsZero(desiredWalkVelocity)) then
doAutoJump();
end
end
end
local function AutoJumper()
local this = {}
local running = false
local runRoutine = nil
function this:Run()
running = true
local thisRoutine = nil
thisRoutine = coroutine.create(function()
while running and thisRoutine == runRoutine do
this:Step()
wait()
end
end)
runRoutine = thisRoutine
coroutine.resume(thisRoutine)
end
function this:Stop()
running = false
end
function this:Step()
preStepSimulatorSide()
end
return this
end
-----------------------------------------------------------------------------
-----------------------------------PATHER--------------------------------------
local function CreateDestinationIndicator(pos)
local destinationGlobe = Create'Part'
{
Name = 'PathGlobe';
TopSurface = 'Smooth';
BottomSurface = 'Smooth';
Shape = 'Ball';
CanCollide = false;
Size = Vector3.new(2,2,2);
BrickColor = BrickColor.new('Institutional white');
Transparency = 0;
Anchored = true;
CFrame = CFrame.new(pos);
}
return destinationGlobe
end
local function Pather(character, point)
local this = {}
this.Cancelled = false
this.Started = false
this.Finished = Signal.Create()
this.PathFailed = Signal.Create()
this.PathStarted = Signal.Create()
this.PathComputed = false
function this:YieldUntilPointReached(character, point, timeout)
timeout = timeout or 10000000
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
local start = tick()
local lastMoveTo = start
while torso and tick() - start < timeout and this.Cancelled == false do
local diffVector = (point - torso.CFrame.p)
local xzMagnitude = (diffVector * Vector3.new(1,0,1)).magnitude
if xzMagnitude < 6 then
-- Jump if the path is telling is to go upwards
if diffVector.Y >= 2.2 then
humanoid.Jump = true
end
end
-- The hard-coded number 2 here is from the engine's MoveTo implementation
if xzMagnitude < 2 then
return true
end
-- Keep on issuing the move command because it will automatically quit every so often.
if tick() - lastMoveTo > 1.5 then
humanoid:MoveTo(point)
lastMoveTo = tick()
end
CharacterControl:UpdateTorso(point)
wait()
end
return false
end
function this:Cancel()
this.Cancelled = true
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
if humanoid and torso then
humanoid:MoveTo(torso.CFrame.p)
end
end
function this:CheckOcclusion(point1, point2, character, torsoRadius)
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
if torsoRadius == nil then
torsoRadius = torso and Vector3.new(torso.Size.X/2,0,torso.Size.Z/2) or Vector3.new(1,0,1)
end
local diffVector = point2 - point1
local directionVector = diffVector.unit
local rightVector = Vector3.new(0,1,0):Cross(directionVector) * torsoRadius
local rightPart, _ = Utility.Raycast(Ray.new(point1 + rightVector, diffVector + rightVector), true, {character})
local hitPart, _ = Utility.Raycast(Ray.new(point1, diffVector), true, {character})
local leftPart, _ = Utility.Raycast(Ray.new(point1 - rightVector, diffVector - rightVector), true, {character})
if rightPart or hitPart or leftPart then
return false
end
-- Make sure we have somewhere to stand on
local midPt = (point2 + point1) / 2
local studsBetweenSamples = 2
for i = 1, math.floor(diffVector.magnitude/studsBetweenSamples) do
local downPart, _ = Utility.Raycast(Ray.new(point1 + directionVector * i * studsBetweenSamples, Vector3.new(0,-7,0)), true, {character})
if not downPart then
return false
end
end
return true
end
function this:SmoothPoints(pathToSmooth)
local result = {}
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
for i = 1, #pathToSmooth do
table.insert(result, pathToSmooth[i])
end
-- Backwards for safe-deletion
for i = #result - 1, 1, -1 do
if i + 1 <= #result then
local nextPoint = result[i+1]
local thisPoint = result[i]
local lastPoint = result[i-1]
if lastPoint == nil then
lastPoint = torso and Vector3.new(torso.CFrame.p.X, thisPoint.Y, torso.CFrame.p.Z)
end
if lastPoint and Utility.FuzzyEquals(thisPoint.Y, lastPoint.Y) and Utility.FuzzyEquals(thisPoint.Y, nextPoint.Y) then
if this:CheckOcclusion(lastPoint, nextPoint, character) then
table.remove(result, i)
-- Move i back one to recursively-smooth
i = i + 1
end
end
end
end
return result
end
function this:CheckNeighboringCells(character)
local pathablePoints = {}
local humanoid = findPlayerHumanoid(Player)
local torso = character and humanoid and humanoid.Torso
if torso then
local torsoCFrame = torso.CFrame
local torsoPos = torsoCFrame.p
-- Minus and plus 2 is so we can get it into the cell-corner space and then translate it back into cell-center space
local roundedPos = Vector3.new(Utility.Round(torsoPos.X-2,4)+2, Utility.Round(torsoPos.Y-2,4)+2, Utility.Round(torsoPos.Z-2,4)+2)
local neighboringCells = {}
for x = -4, 4, 8 do
for z = -4, 4, 8 do
table.insert(neighboringCells, roundedPos + Vector3.new(x,0,z))
end
end
for _, testPoint in pairs(neighboringCells) do
local pathable = this:CheckOcclusion(roundedPos, testPoint, character, Vector3.new(0,0,0))
if pathable then
table.insert(pathablePoints, testPoint)
end
end
end
return pathablePoints
end
function this:ComputeDirectPath()
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
if torso then
local startPt = torso.CFrame.p
local finishPt = point
if (finishPt - startPt).magnitude < 150 then
-- move back the destination by 2 studs or otherwise the pather will collide with the object we are trying to reach
finishPt = finishPt - (finishPt - startPt).unit * 2
if this:CheckOcclusion(startPt, finishPt, character, Vector3.new(0,0,0)) then
local pathResult = {}
pathResult.Status = Enum.PathStatus.Success
function pathResult:GetPointCoordinates()
return {finishPt}
end
return pathResult
end
end
end
end
local function AllAxisInThreshhold(targetPt, otherPt, threshold)
return math.abs(targetPt.X - otherPt.X) <= threshold and
math.abs(targetPt.Y - otherPt.Y) <= threshold and
math.abs(targetPt.Z - otherPt.Z) <= threshold
end
function this:ComputePath()
local smoothed = false
local humanoid = findPlayerHumanoid(Player)
local torso = humanoid and humanoid.Torso
if torso then
if this.PathComputed then return end
this.PathComputed = true
-- Will yield the script since it is an Async script (start, finish, maxDistance)
-- Try to use the smooth function, but it may not exist yet :(
local success = pcall(function()
-- 3 is height from torso cframe to ground
this.pathResult = PathfindingService:ComputeSmoothPathAsync(torso.CFrame.p - Vector3.new(0,3,0), point, 400)
smoothed = true
end)
if not success then
-- 3 is height from torso cframe to ground
this.pathResult = PathfindingService:ComputeRawPathAsync(torso.CFrame.p - Vector3.new(0,3,0), point, 400)
smoothed = false
end
this.pointList = this.pathResult and this.pathResult:GetPointCoordinates()
local pathFound = false
if this.pathResult.Status == Enum.PathStatus.FailFinishNotEmpty then
-- Lets try again with a slightly set back start point; it is ok to do this again so the FailFinishNotEmpty uses little computation
local diffVector = point - workspace.CurrentCamera.CoordinateFrame.p
if diffVector.magnitude > 2 then
local setBackPoint = point - (diffVector).unit * 2.1
local success = pcall(function()
this.pathResult = PathfindingService:ComputeSmoothPathAsync(torso.CFrame.p, setBackPoint, 400)
smoothed = true
end)
if not success then
this.pathResult = PathfindingService:ComputeRawPathAsync(torso.CFrame.p, setBackPoint, 400)
smoothed = false
end
this.pointList = this.pathResult and this.pathResult:GetPointCoordinates()
pathFound = true
end
end
if this.pathResult.Status == Enum.PathStatus.ClosestNoPath and #this.pointList >= 1 and pathFound == false then
local otherPt = this.pointList[#this.pointList]
if AllAxisInThreshhold(point, otherPt, 4) and (torso.CFrame.p - point).magnitude > (otherPt - point).magnitude then
local pathResult = {}
pathResult.Status = Enum.PathStatus.Success
function pathResult:GetPointCoordinates()
return {this.pointList}
end
this.pathResult = pathResult
pathFound = true
end
end
if (this.pathResult.Status == Enum.PathStatus.FailStartNotEmpty or this.pathResult.Status == Enum.PathStatus.ClosestNoPath) and pathFound == false then
local pathablePoints = this:CheckNeighboringCells(character)
for _, otherStart in pairs(pathablePoints) do
local pathResult;
local success = pcall(function()
pathResult = PathfindingService:ComputeSmoothPathAsync(otherStart, point, 400)
smoothed = true
end)
if not success then
pathResult = PathfindingService:ComputeRawPathAsync(otherStart, point, 400)
smoothed = false
end
if pathResult and pathResult.Status == Enum.PathStatus.Success then
this.pathResult = pathResult
if this.pathResult then
this.pointList = this.pathResult:GetPointCoordinates()
table.insert(this.pointList, 1, otherStart)
end
break
end
end
end
if DirectPathEnabled then
if this.pathResult.Status ~= Enum.PathStatus.Success then
local directPathResult = this:ComputeDirectPath()
if directPathResult and directPathResult.Status == Enum.PathStatus.Success then
this.pathResult = directPathResult
this.pointList = directPathResult:GetPointCoordinates()
end
end
end
end
return smoothed
end
function this:IsValidPath()
this:ComputePath()
local pathStatus = this.pathResult.Status
return pathStatus == Enum.PathStatus.Success
end
function this:GetPathStatus()
this:ComputePath()
return this.pathResult.Status
end
function this:Start()
if CurrentSeatPart then
return
end
spawn(function()
local humanoid = findPlayerHumanoid(Player)
--humanoid.AutoRotate = false
local torso = humanoid and humanoid.Torso
if torso then
if this.Started then return end
this.Started = true
-- Will yield the script since it is an Async function script (start, finish, maxDistance)
local smoothed = this:ComputePath()
if this:IsValidPath() then
this.PathStarted:fire()
-- smooth out zig-zaggy paths
local smoothPath = smoothed and this.pointList or this:SmoothPoints(this.pointList)
for i, point in pairs(smoothPath) do
if humanoid then
if this.Cancelled then
return
end
local wayPoint = nil
if SHOW_PATH then
wayPoint = CreateDestinationIndicator(point)
wayPoint.BrickColor = BrickColor.new("New Yeller")
wayPoint.Parent = workspace
print(wayPoint.CFrame.p)
end
humanoid:MoveTo(point)
local distance = ((torso.CFrame.p - point) * Vector3.new(1,0,1)).magnitude
local approxTime = 10
if math.abs(humanoid.WalkSpeed) > 0 then
approxTime = distance / math.abs(humanoid.WalkSpeed)
end
local yielding = true
if i == 1 then
--local rotatedCFrame = CameraModule:LookAtPreserveHeight(point)
if CameraModule then
local rotatedCFrame = CameraModule:LookAtPreserveHeight(smoothPath[#smoothPath])
local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame)
end
--CharacterControl:SetTorsoLookPoint(point)
end
---[[
if (humanoid.Torso.CFrame.p - point).magnitude > 9 then
spawn(function()
while yielding and this.Cancelled == false do
if CameraModule then
local look = CameraModule:GetCameraLook()
local squashedLook = (look * Vector3.new(1,0,1)).unit
local direction = ((point - CameraModule.cframe.p) * Vector3.new(1,0,1)).unit
local theta = math.deg(math.acos(squashedLook:Dot(direction)))
if tick() - Utility.GetLastInput() > 2 and theta > (workspace.CurrentCamera.FieldOfView / 2) then
local rotatedCFrame = CameraModule:LookAtPreserveHeight(point)
local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame)
--return
end
end
wait(0.1)
end
end)
end
--]]
local didReach = this:YieldUntilPointReached(character, point, approxTime * 3 + 1)
yielding = false
if SHOW_PATH then
wayPoint:Destroy()
end
if not didReach then
this.PathFailed:fire()
return
end
end
end
this.Finished:fire()
return
end
end
this.PathFailed:fire()
end)
end
return this
end
-------------------------------------------------------------------------
local function FlashRed(object)
local origColor = object.BrickColor
local redColor = BrickColor.new("Really red")
local start = tick()
local duration = 4
spawn(function()
while object and tick() - start < duration do
object.BrickColor = origColor
wait(0.13)
if object then
object.BrickColor = redColor
end
wait(0.13)
end
end)
end
--local joystickWidth = 250
--local joystickHeight = 250
local function IsInBottomLeft(pt)
local joystickHeight = math.min(Utility.ViewSizeY() * 0.33, 250)
local joystickWidth = joystickHeight
return pt.X <= joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
end
local function IsInBottomRight(pt)
local joystickHeight = math.min(Utility.ViewSizeY() * 0.33, 250)
local joystickWidth = joystickHeight
return pt.X >= Utility.ViewSizeX() - joystickWidth and pt.Y > Utility.ViewSizeY() - joystickHeight
end
local function CheckAlive(character)
local humanoid = findPlayerHumanoid(Player)
return humanoid ~= nil and humanoid.Health > 0
end
local function GetEquippedTool(character)
if character ~= nil then
for _, child in pairs(character:GetChildren()) do
if child:IsA('Tool') then
return child
end
end
end
end
local function ExploreWithRayCast(currentPoint, originDirection)
local TestDistance = 40
local TestVectors = {}
do
local forwardVector = originDirection;
for i = 0, 15 do
table.insert(TestVectors, CFrame.Angles(0, math.pi / 8 * i, 0) * forwardVector)
end
end
local testResults = {}
-- Heuristic should be something along the lines of distance and closeness to the traveling direction
local function ExploreHeuristic()
for _, testData in pairs(testResults) do
local walkDirection = -1 * originDirection
local directionCoeff = (walkDirection:Dot(testData['Vector']) + 1) / 2
local distanceCoeff = testData['Distance'] / TestDistance
testData["Value"] = directionCoeff * distanceCoeff
end
end
for i, vec in pairs(TestVectors) do
local hitPart, hitPos = Utility.Raycast(Ray.new(currentPoint, vec * TestDistance), true, {Player.Character})
if hitPos then
table.insert(testResults, {Vector = vec; Distance = (hitPos - currentPoint).magnitude})
else
table.insert(testResults, {Vector = vec; Distance = TestDistance})
end
end
ExploreHeuristic()
table.sort(testResults, function(a,b) return a["Value"] > b["Value"] end)
return testResults
end
local TapId = 1
local ExistingPather = nil
local ExistingIndicator = nil
local PathCompleteListener = nil
local PathFailedListener = nil
local function CleanupPath()
DrivingTo = nil
if ExistingPather then
ExistingPather:Cancel()
end
if PathCompleteListener then
PathCompleteListener:disconnect()
PathCompleteListener = nil
end
if PathFailedListener then
PathFailedListener:disconnect()
PathFailedListener = nil
end
if ExistingIndicator then
DebrisService:AddItem(ExistingIndicator, 0)
ExistingIndicator = nil
end
end
local function getExtentsSize(Parts)
local maxX = Parts[1].Position.X
local maxY = Parts[1].Position.Y
local maxZ = Parts[1].Position.Z
local minX = Parts[1].Position.X
local minY = Parts[1].Position.Y
local minZ = Parts[1].Position.Z
for i = 2, #Parts do
maxX = math.max(maxX, Parts[i].Position.X)
maxY = math.max(maxY, Parts[i].Position.Y)
maxZ = math.max(maxZ, Parts[i].Position.Z)
minX = math.min(minX, Parts[i].Position.X)
minY = math.min(minY, Parts[i].Position.Y)
minZ = math.min(minZ, Parts[i].Position.Z)
end
return Region3.new(Vector3.new(minX, minY, minZ), Vector3.new(maxX, maxY, maxZ))
end
local function inExtents(Extents, Position)
if Position.X < (Extents.CFrame.p.X - Extents.Size.X/2) or Position.X > (Extents.CFrame.p.X + Extents.Size.X/2) then
return false
end
if Position.Z < (Extents.CFrame.p.Z - Extents.Size.Z/2) or Position.Z > (Extents.CFrame.p.Z + Extents.Size.Z/2) then
return false
end
--ignoring Y for now
return true
end
local AutoJumperInstance = nil
local ShootCount = 0
local FailCount = 0
local function OnTap(tapPositions, goToPoint)
-- Good to remember if this is the latest tap event
TapId = TapId + 1
local thisTapId = TapId
local camera = workspace.CurrentCamera
local character = Player.Character
if not CheckAlive(character) then return end
-- This is a path tap position
if #tapPositions == 1 or goToPoint then
if camera then
local unitRay = Utility.GetUnitRay(tapPositions[1].x, tapPositions[1].y, MyMouse.ViewSizeX, MyMouse.ViewSizeY, camera)
local ray = Ray.new(unitRay.Origin, unitRay.Direction*400)
local hitPart, hitPt = Utility.Raycast(ray, true, {character})
local hitChar, hitHumanoid = Utility.FindChacterAncestor(hitPart)
local torso = character and character:FindFirstChild("Humanoid") and character:FindFirstChild("Humanoid").Torso
local startPos = torso.CFrame.p
if goToPoint then
hitPt = goToPoint
hitChar = nil
end
if hitChar and hitHumanoid and hitHumanoid.Torso and (hitHumanoid.Torso.CFrame.p - torso.CFrame.p).magnitude < 7 then
CleanupPath()
local myHumanoid = findPlayerHumanoid(Player)
if myHumanoid then
myHumanoid:MoveTo(hitPt)
end
ShootCount = ShootCount + 1
local thisShoot = ShootCount
-- Do shooot
local currentWeapon = GetEquippedTool(character)
if currentWeapon then
currentWeapon:Activate()
LastFired = tick()
end
elseif hitPt and character and not CurrentSeatPart then
local thisPather = Pather(character, hitPt)
if thisPather:IsValidPath() then
FailCount = 0
-- TODO: Remove when bug in engine is fixed
Player:Move(Vector3.new(1, 0, 0))
Player:Move(Vector3.new(0, 0, 0))
thisPather:Start()
if BindableEvent_OnFailStateChanged then
BindableEvent_OnFailStateChanged:Fire(false)
end
CleanupPath()
local destinationGlobe = CreateDestinationIndicator(hitPt)
destinationGlobe.Parent = camera
ExistingPather = thisPather
ExistingIndicator = destinationGlobe
if AutoJumperInstance then
AutoJumperInstance:Run()
end
PathCompleteListener = thisPather.Finished:connect(function()
if AutoJumperInstance then
AutoJumperInstance:Stop()
end
if destinationGlobe then
if ExistingIndicator == destinationGlobe then
ExistingIndicator = nil
end
DebrisService:AddItem(destinationGlobe, 0)
destinationGlobe = nil
end
if hitChar then
local humanoid = findPlayerHumanoid(Player)
ShootCount = ShootCount + 1
local thisShoot = ShootCount
-- Do shoot
local currentWeapon = GetEquippedTool(character)
if currentWeapon then
currentWeapon:Activate()
LastFired = tick()
end
if humanoid then
humanoid:MoveTo(hitPt)
end
end
local finishPos = torso and torso.CFrame.p --hitPt
if finishPos and startPos and tick() - Utility.GetLastInput() > 2 then
local exploreResults = ExploreWithRayCast(finishPos, ((startPos - finishPos) * Vector3.new(1,0,1)).unit)
-- Check for Nans etc..
if exploreResults[1] and exploreResults[1]["Vector"] and exploreResults[1]["Vector"].magnitude >= 0.5 and exploreResults[1]["Distance"] > 3 then
if CameraModule then
local rotatedCFrame = CameraModule:LookAtPreserveHeight(finishPos + exploreResults[1]["Vector"] * exploreResults[1]["Distance"])
local finishedSignal, duration = CameraModule:TweenCameraLook(rotatedCFrame)
end
end
end
end)
PathFailedListener = thisPather.PathFailed:connect(function()
if AutoJumperInstance then
AutoJumperInstance:Stop()
end
if destinationGlobe then
FlashRed(destinationGlobe)
DebrisService:AddItem(destinationGlobe, 3)
end
end)
else
if hitPt then
-- Feedback here for when we don't have a good path
local failedGlobe = CreateDestinationIndicator(hitPt)
FlashRed(failedGlobe)
DebrisService:AddItem(failedGlobe, 1)
failedGlobe.Parent = camera
if ExistingIndicator == nil then
FailCount = FailCount + 1
if FailCount >= 3 then
if BindableEvent_OnFailStateChanged then
BindableEvent_OnFailStateChanged:Fire(true)
end
CleanupPath()
end
end
end
end
elseif hitPt and character and CurrentSeatPart then
local destinationGlobe = CreateDestinationIndicator(hitPt)
destinationGlobe.Parent = camera
ExistingIndicator = destinationGlobe
DrivingTo = hitPt
local ConnectedParts = CurrentSeatPart:GetConnectedParts(true)
while wait() do
if CurrentSeatPart and ExistingIndicator == destinationGlobe then
local ExtentsSize = getExtentsSize(ConnectedParts)
if inExtents(ExtentsSize, destinationGlobe.Position) then
DebrisService:AddItem(destinationGlobe, 0)
destinationGlobe = nil
DrivingTo = nil
break
end
else
DebrisService:AddItem(destinationGlobe, 0)
if CurrentSeatPart == nil and destinationGlobe == ExistingIndicator then
DrivingTo = nil
OnTap(tapPositions, hitPt)
end
destinationGlobe = nil
break
end
end
else
-- no hit pt
end
end
elseif #tapPositions >= 2 then
if camera then
ShootCount = ShootCount + 1
local thisShoot = ShootCount
-- Do shoot
local avgPoint = Utility.AveragePoints(tapPositions)
local unitRay = Utility.GetUnitRay(avgPoint.x, avgPoint.y, MyMouse.ViewSizeX, MyMouse.ViewSizeY, camera)
local currentWeapon = GetEquippedTool(character)
if currentWeapon then
currentWeapon:Activate()
LastFired = tick()
end
end
end
end
local function CreateClickToMoveModule()
local this = {}
local LastStateChange = 0
local LastState = Enum.HumanoidStateType.Running
local FingerTouches = {}
local NumUnsunkTouches = 0
-- PC simulation
local mouse1Down = tick()
local mouse1DownPos = Vector2.new()
local mouse2Down = tick()
local mouse2DownPos = Vector2.new()
local mouse2Up = tick()
local movementKeys = {
[Enum.KeyCode.W] = true;
[Enum.KeyCode.A] = true;
[Enum.KeyCode.S] = true;
[Enum.KeyCode.D] = true;
[Enum.KeyCode.Up] = true;
[Enum.KeyCode.Down] = true;
}
local TapConn = nil
local InputBeganConn = nil
local InputChangedConn = nil
local InputEndedConn = nil
local HumanoidDiedConn = nil
local CharacterChildAddedConn = nil
local OnCharacterAddedConn = nil
local CharacterChildRemovedConn = nil
local RenderSteppedConn = nil
local HumanoidSeatedConn = nil
local function disconnectEvent(event)
if event then
event:disconnect()
end
end
local function DisconnectEvents()
disconnectEvent(TapConn)
disconnectEvent(InputBeganConn)
disconnectEvent(InputChangedConn)
disconnectEvent(InputEndedConn)
disconnectEvent(HumanoidDiedConn)
disconnectEvent(CharacterChildAddedConn)
disconnectEvent(OnCharacterAddedConn)
disconnectEvent(RenderSteppedConn)
disconnectEvent(CharacterChildRemovedConn)
pcall(function() RunService:UnbindFromRenderStep("ClickToMoveRenderUpdate") end)
disconnectEvent(HumanoidSeatedConn)
end
local function IsFinite(num)
return num == num and num ~= 1/0 and num ~= -1/0
end
local function findAngleBetweenXZVectors(vec2, vec1)
return math.atan2(vec1.X*vec2.Z-vec1.Z*vec2.X, vec1.X*vec2.X + vec1.Z*vec2.Z)
end
-- Setup the camera
CameraModule = ClassicCameraModule()
do
-- Extend The Camera Module Class
function CameraModule:LookAtPreserveHeight(newLookAtPt)
local camera = workspace.CurrentCamera
local focus = camera.Focus.p
local cameraCFrame = CameraModule.cframe
local mag = Vector3.new(cameraCFrame.lookVector.x, 0, cameraCFrame.lookVector.z).magnitude
local newLook = (Vector3.new(newLookAtPt.x, focus.y, newLookAtPt.z) - focus).unit * mag
local flippedLook = newLook + Vector3.new(0, cameraCFrame.lookVector.y, 0)
local distance = (focus - cameraCFrame.p).magnitude
local newCamPos = focus - flippedLook.unit * distance
return CFrame.new(newCamPos, newCamPos + flippedLook)
end
function CameraModule:TweenCameraLook(desiredCFrame, speed)
local e = 2.718281828459
local function SCurve(t)
return 1/(1 + e^(-t*1.5))
end
local function easeOutSine(t, b, c, d)
if t >= d then return b + c end
return c * math.sin(t/d * (math.pi/2)) + b;
end
local theta, interper = CFrameInterpolator(CFrame.new(Vector3.new(), self:GetCameraLook()), desiredCFrame - desiredCFrame.p)
theta = Utility.Clamp(0, math.pi, theta)
local duration = 0.65 * SCurve(theta - math.pi/4) + 0.15
if speed then
duration = theta / speed
end
local start = tick()
local finish = start + duration
self.UpdateTweenFunction = function()
local currTime = tick() - start
local alpha = Utility.Clamp(0, 1, easeOutSine(currTime, 0, 1, duration))
local newCFrame = interper(alpha)
local y = findAngleBetweenXZVectors(newCFrame.lookVector, self:GetCameraLook())
if IsFinite(y) and math.abs(y) > 0.0001 then
self.RotateInput = self.RotateInput + Vector2.new(y, 0)
end
return (currTime >= finish or alpha >= 1)
end
end
end
--- Done Extending
local function OnTouchBegan(input, processed)
if FingerTouches[input] == nil and not processed then
NumUnsunkTouches = NumUnsunkTouches + 1
end
FingerTouches[input] = processed
end
local function OnTouchChanged(input, processed)
if FingerTouches[input] == nil then
FingerTouches[input] = processed
if not processed then
NumUnsunkTouches = NumUnsunkTouches + 1
end
end
end
local function OnTouchEnded(input, processed)
--print("Touch tap fake:" , processed)
--if not processed then
-- OnTap({input.Position})
--end
if FingerTouches[input] ~= nil and FingerTouches[input] == false then
NumUnsunkTouches = NumUnsunkTouches - 1
end
FingerTouches[input] = nil
end
local function OnCharacterAdded(character)
DisconnectEvents()
InputBeganConn = UIS.InputBegan:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchBegan(input, processed)
-- Give back controls when they tap both sticks
local wasInBottomLeft = IsInBottomLeft(input.Position)
local wasInBottomRight = IsInBottomRight(input.Position)
if wasInBottomRight or wasInBottomLeft then
for otherInput, _ in pairs(FingerTouches) do
if otherInput ~= input then
local otherInputInLeft = IsInBottomLeft(otherInput.Position)
local otherInputInRight = IsInBottomRight(otherInput.Position)
if otherInput.UserInputState ~= Enum.UserInputState.End and ((wasInBottomLeft and otherInputInRight) or (wasInBottomRight and otherInputInLeft)) then
if BindableEvent_OnFailStateChanged then
BindableEvent_OnFailStateChanged:Fire(true)
end
return
end
end
end
end
end
-- Cancel path when you use the keyboard controls.
if processed == false and input.UserInputType == Enum.UserInputType.Keyboard and movementKeys[input.KeyCode] then
CleanupPath()
end
if input.UserInputType == Enum.UserInputType.MouseButton1 then
mouse1Down = tick()
mouse1DownPos = input.Position
end
if input.UserInputType == Enum.UserInputType.MouseButton2 then
mouse2Down = tick()
mouse2DownPos = input.Position
end
end)
InputChangedConn = UIS.InputChanged:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchChanged(input, processed)
end
end)
InputEndedConn = UIS.InputEnded:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchEnded(input, processed)
end
if input.UserInputType == Enum.UserInputType.MouseButton2 then
mouse2Up = tick()
local currPos = input.Position
if mouse2Up - mouse2Down < 0.25 and (currPos - mouse2DownPos).magnitude < 5 then
local positions = {currPos}
OnTap(positions)
end
end
end)
TapConn = UIS.TouchTap:connect(function(touchPositions, processed)
if not processed then
OnTap(touchPositions)
end
end)
if not UIS.TouchEnabled then -- PC
if AutoJumperInstance then
AutoJumperInstance:Stop()
AutoJumperInstance = nil
end
AutoJumperInstance = AutoJumper()
end
local function getThrottleAndSteer(object, point)
local lookVector = (point - object.Position)
lookVector = Vector3.new(lookVector.X, 0, lookVector.Z).unit
local objectVector = Vector3.new(object.CFrame.lookVector.X, 0, object.CFrame.lookVector.Z).unit
local dirVector = lookVector - objectVector
local mag = dirVector.magnitude
local degrees = math.deg(math.acos(lookVector:Dot(objectVector)))
local side = (object.CFrame:pointToObjectSpace(point).X > 0)
local throttle = 0
if mag < 0.25 then
throttle = 1
end
if mag > 1.8 then
throttle = -1
end
local distance = CurrentSeatPart.Position - DrivingTo
local velocity = CurrentSeatPart.Velocity
if velocity.magnitude*1.5 > distance.magnitude then
if velocity.magnitude*0.5 > distance.magnitude then
throttle = -throttle
else
throttle = 0
end
end
local steer = 0
if degrees > 5 and degrees < 175 then
if side then
steer = 1
else
steer = -1
end
end
local rotatingAt = math.deg(CurrentSeatPart.RotVelocity.magnitude)
local degreesAway = math.max(math.min(degrees, 180 - degrees), 10)
if (CurrentSeatPart.RotVelocity.X < 0)== (steer < 0) then
if rotatingAt*1.5 > degreesAway then
if rotatingAt*0.5 > degreesAway then
steer = -steer
else
steer = 0
end
end
end
return throttle, steer
end
local function Update()
if CameraModule then
if CameraModule.UserPanningTheCamera then
CameraModule.UpdateTweenFunction = nil
else
if CameraModule.UpdateTweenFunction then
local done = CameraModule.UpdateTweenFunction()
if done then
CameraModule.UpdateTweenFunction = nil
end
end
end
CameraModule:Update()
end
if CurrentSeatPart then
if DrivingTo then
local throttle, steer = getThrottleAndSteer(CurrentSeatPart, DrivingTo)
CurrentSeatPart.Throttle = throttle
CurrentSeatPart.Steer = steer
end
end
end
local success = pcall(function() RunService:BindToRenderStep("ClickToMoveRenderUpdate",Enum.RenderPriority.Camera.Value - 1,Update) end)
if not success then
if RenderSteppedConn then
RenderSteppedConn:disconnect()
end
RenderSteppedConn = RunService.RenderStepped:connect(Update)
end
local WasAutoJumper = false
local WasAutoJumpMobile = false
local function onSeated(child, active, currentSeatPart)
if active then
if BindableEvent_EnableTouchJump then
BindableEvent_EnableTouchJump:Fire(true)
end
if currentSeatPart.ClassName == "VehicleSeat" then
CurrentSeatPart = currentSeatPart
if AutoJumperInstance then
AutoJumperInstance:Stop()
AutoJumperInstance = nil
WasAutoJumper = true
else
WasAutoJumper = false
end
if child.AutoJumpEnabled then
WasAutoJumpMobile = true
child.AutoJumpEnabled = false
end
end
else
CurrentSeatPart = nil
if BindableEvent_EnableTouchJump then
BindableEvent_EnableTouchJump:Fire(false)
end
if WasAutoJumper then
AutoJumperInstance = AutoJumper()
WasAutoJumper = false
end
if WasAutoJumpMobile then
child.AutoJumpEnabled = true
WasAutoJumpMobile = false
end
end
end
local function OnCharacterChildAdded(child)
if UIS.TouchEnabled then
if child:IsA('Tool') then
child.ManualActivationOnly = true
end
end
if child:IsA('Humanoid') then
disconnectEvent(HumanoidDiedConn)
HumanoidDiedConn = child.Died:connect(function()
DebrisService:AddItem(ExistingIndicator, 1)
if AutoJumperInstance then
AutoJumperInstance:Stop()
AutoJumperInstance = nil
end
end)
local WasAutoJumper = false
local WasAutoJumpMobile = false
HumanoidSeatedConn = child.Seated:connect(function(active, seat) onSeated(child, active, seat) end)
if child.SeatPart then
onSeated(child, true, child.SeatPart)
end
end
end
CharacterChildAddedConn = character.ChildAdded:connect(function(child)
OnCharacterChildAdded(child)
end)
CharacterChildRemovedConn = character.ChildRemoved:connect(function(child)
if UIS.TouchEnabled then
if child:IsA('Tool') then
child.ManualActivationOnly = false
end
end
end)
for _, child in pairs(character:GetChildren()) do
OnCharacterChildAdded(child)
end
end
local Running = false
function this:Stop()
if Running then
DisconnectEvents()
CleanupPath()
if AutoJumperInstance then
AutoJumperInstance:Stop()
AutoJumperInstance = nil
end
if CameraModule then
CameraModule.UpdateTweenFunction = nil
CameraModule:SetEnabled(false)
end
-- Restore tool activation on shutdown
if UIS.TouchEnabled then
local character = Player.Character
if character then
for _, child in pairs(character:GetChildren()) do
if child:IsA('Tool') then
child.ManualActivationOnly = false
end
end
end
end
DrivingTo = nil
Running = false
end
end
function this:Start()
if not Running then
if Player.Character then -- retro-listen
OnCharacterAdded(Player.Character)
end
OnCharacterAddedConn = Player.CharacterAdded:connect(OnCharacterAdded)
if CameraModule then
CameraModule:SetEnabled(true)
end
Running = true
end
end
return this
end
return CreateClickToMoveModule
]]>
-
Invisicam
originalFade then
hit.LocalTransparencyModifier = math.max(originalFade, currentFade - FADE_RATE)
else
SavedHits[hit] = nil
end
end
end
end
function Invisicam:SetMode(newMode)
AssertTypes(newMode, 'number')
for modeName, modeNum in pairs(MODE) do
if modeNum == newMode then
Mode = newMode
return
end
end
error("Invalid mode number")
end
function Invisicam:SetCustomBehavior(func)
AssertTypes(func, 'function')
Behaviors[MODE.CUSTOM] = func
end
-- Want to turn off Invisicam? Be sure to call this after.
function Invisicam:Cleanup()
for hit, originalFade in pairs(SavedHits) do
hit.LocalTransparencyModifier = originalFade
end
end
---------------------
--| Running Logic |--
---------------------
-- Connect to the current and all future cameras
workspace.Changed:connect(OnWorkspaceChanged)
OnWorkspaceChanged('CurrentCamera')
Player.CharacterAdded:connect(OnCharacterAdded)
if Player.Character then
OnCharacterAdded(Player.Character)
end
Invisicam:SetMode(STARTING_MODE)
Behaviors[MODE.CUSTOM] = function() end -- (Does nothing until SetCustomBehavior)
Behaviors[MODE.LIMBS] = LimbBehavior
Behaviors[MODE.MOVEMENT] = MoveBehavior
Behaviors[MODE.CORNERS] = CornerBehavior
Behaviors[MODE.CIRCLE1] = CircleBehavior
Behaviors[MODE.CIRCLE2] = CircleBehavior
Behaviors[MODE.LIMBMOVE] = LimbMoveBehavior
return Invisicam
]]>
-
PopperCam
0.95 or hitPart.CanCollide == false) then
table.insert(ignoreList, hitPart)
else
return hitPart, hitPoint
end
until false
end
local function ScreenToWorld(screenPoint, screenSize, pushDepth)
local cameraFOV, cameraCFrame = Camera.FieldOfView, Camera.CoordinateFrame
local imagePlaneDepth = screenSize.y / (2 * math.tan(math.rad(cameraFOV) / 2))
local direction = Vector3.new(screenPoint.x - (screenSize.x / 2), (screenSize.y / 2) - screenPoint.y, -imagePlaneDepth)
local worldDirection = (cameraCFrame:vectorToWorldSpace(direction)).Unit
local theta = math.acos(math.min(1, worldDirection:Dot(cameraCFrame.lookVector)))
local fixedPushDepth = pushDepth / math.sin((math.pi / 2) - theta)
return cameraCFrame.p + worldDirection * fixedPushDepth
end
local function OnCameraChanged(property)
if property == 'CameraSubject' then
VehicleParts = {}
local newSubject = Camera.CameraSubject
if newSubject then
-- Determine if we should be popping at all
PopperEnabled = false
for _, subjectType in pairs(VALID_SUBJECTS) do
if newSubject:IsA(subjectType) then
PopperEnabled = true
break
end
end
-- Get all parts of the vehicle the player is controlling
if newSubject:IsA('VehicleSeat') then
VehicleParts = newSubject:GetConnectedParts(true)
end
end
end
end
local function OnCharacterAdded(player, character)
PlayerCharacters[player] = character
end
local function OnPlayersChildAdded(child)
if child:IsA('Player') then
child.CharacterAdded:connect(function(character)
OnCharacterAdded(child, character)
end)
if child.Character then
OnCharacterAdded(child, child.Character)
end
end
end
local function OnPlayersChildRemoved(child)
if child:IsA('Player') then
PlayerCharacters[child] = nil
end
end
local function OnWorkspaceChanged(property)
if property == 'CurrentCamera' then
local newCamera = workspace.CurrentCamera
if newCamera then
Camera = newCamera
if CameraChangeConn then
CameraChangeConn:disconnect()
end
CameraChangeConn = Camera.Changed:connect(OnCameraChanged)
OnCameraChanged('CameraSubject')
end
end
end
-------------------------
--| Exposed Functions |--
-------------------------
function PopperCam:Update()
if PopperEnabled then
-- First, prep some intermediate vars
local focusPoint = Camera.Focus.p
local cameraCFrame = Camera.CoordinateFrame
local cameraFrontPoint = cameraCFrame.p + (cameraCFrame.lookVector * NEAR_CLIP_PLANE_OFFSET)
local screenSize = Camera.ViewportSize
local ignoreList = {}
for _, character in pairs(PlayerCharacters) do
table.insert(ignoreList, character)
end
for _, basePart in pairs(VehicleParts) do
table.insert(ignoreList, basePart)
end
-- Cast rays at the near clip plane, from corresponding points near the focus point,
-- and find the direct line that is the most cut off
local largest = 0
for _, screenScale in pairs(CAST_SCREEN_SCALES) do
local clipWorldPoint = ScreenToWorld(screenSize * screenScale, screenSize, NEAR_CLIP_PLANE_OFFSET)
local rayStartPoint = focusPoint + (clipWorldPoint - cameraFrontPoint)
local _, hitPoint = PiercingCast(rayStartPoint, clipWorldPoint, ignoreList)
local cutoffAmount = (hitPoint - clipWorldPoint).Magnitude
if cutoffAmount > largest then
largest = cutoffAmount
end
end
-- Then check if the player zoomed since the last frame,
-- and if so, reset our pop history so we stop tweening
local zoomLevel = (cameraCFrame.p - focusPoint).Magnitude
if math.abs(zoomLevel - LastZoomLevel) > 0.001 then
LastPopAmount = 0
end
-- Finally, zoom the camera in (pop) by that most-cut-off amount, or the last pop amount if that's more
local popAmount = math.max(largest, LastPopAmount)
if popAmount > 0 then
Camera.CoordinateFrame = cameraCFrame + (cameraCFrame.lookVector * popAmount)
LastPopAmount = math.max(0, popAmount - POP_RESTORE_RATE) -- Shrink it for the next frame
end
LastZoomLevel = zoomLevel
end
end
--------------------
--| Script Logic |--
--------------------
-- Connect to the current and all future cameras
workspace.Changed:connect(OnWorkspaceChanged)
OnWorkspaceChanged('CurrentCamera')
-- Connect to all Players so we can ignore their Characters
PlayersService.ChildRemoved:connect(OnPlayersChildRemoved)
PlayersService.ChildAdded:connect(OnPlayersChildAdded)
for _, player in pairs(PlayersService:GetPlayers()) do
OnPlayersChildAdded(player)
end
return PopperCam
]]>
-
RootCamera
= 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 cameraChangedConn = 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 newState == Enum.HumanoidStateType.Jumping and not this:IsInFirstPerson() and VRService.VREnabled then
startCameraFreeze(this:GetSubjectPosition(), humanoid)
elseif newState ~= Enum.HumanoidStateType.Jumping and newState ~= Enum.HumanoidStateType.Freefall then
cancelCameraFreeze(true)
end
end)
end
end
local function onCameraChanged(prop)
if prop == 'CameraSubject' then
onNewCameraSubject()
end
end
local function onCurrentCameraChanged()
if cameraChangedConn then
cameraChangedConn:disconnect()
cameraChangedConn = nil
end
local camera = workspace.CurrentCamera
if camera then
camera.Changed:connect(onCameraChanged)
onCameraChanged('CameraSubject')
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 fingerTouches = {}
local NumUnsunkTouches = 0
local StartingDiff = nil
local pinchBeginZoom = nil
this.ZoomEnabled = true
this.PanEnabled = true
this.KeyPanEnabled = true
local function OnTouchBegan(input, processed)
fingerTouches[input] = processed
if not processed then
NumUnsunkTouches = NumUnsunkTouches + 1
end
end
local function OnTouchChanged(input, processed)
if fingerTouches[input] == nil then
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
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 OnTouchEnded(input, processed)
if fingerTouches[input] == false then
if NumUnsunkTouches == 1 then
panBeginLook = nil
startPos = nil
lastPos = nil
this.UserPanningTheCamera = false
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
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
if startPos and lastPos and panBeginLook then
local currPos = lastPos + input.Delta
local totalTrans = currPos - startPos
if this.PanEnabled then
local desiredXYVector = this:MouseTranslationToAngle(input.Delta) * 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(input.Delta) * 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
if UserSettings():IsUserFeatureEnabled("UserBetterInertialScrolling") then
this:ZoomCameraBy(-input.Position.Z/15)
else
this:ZoomCameraBy(clamp(-1, 1, -input.Position.Z) * 1.4)
end
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 ~= Vector3.new(0,0,0) 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 thumbstickSensitivity = 1.0
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 == Vector2.new(0,0) then
lastThumbstickRotate = nil
if lastThumbstickPos == Vector2.new(0,0) 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
finalConstant = thumbstickSensitivity * currentSpeed
lastVelocity = (gamepadPan - lastThumbstickPos)/(currentTime - lastThumbstickRotate)
end
lastThumbstickPos = gamepadPan
lastThumbstickRotate = currentTime
return Vector2.new( gamepadPan.X * finalConstant, gamepadPan.Y * finalConstant * ySensitivity)
end
return Vector2.new(0,0)
end
local InputBeganConn, InputChangedConn, InputEndedConn, MenuOpenedConn, ShiftLockToggleConn, GamepadConnectedConn, GamepadDisconnectedConn = 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 cameraChangedConn then
cameraChangedConn:disconnect()
cameraChangedConn = nil
end
if workspaceChangedConn then
workspaceChangedConn:disconnect()
workspaceChangedConn = 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:ConnectInputEvents()
InputBeganConn = UserInputService.InputBegan:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchBegan(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
OnMouse2Down(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
OnMouse3Down(input, processed)
end
-- Keyboard
if input.UserInputType == Enum.UserInputType.Keyboard then
OnKeyDown(input, processed)
end
end)
InputChangedConn = UserInputService.InputChanged:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchChanged(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseMovement then
OnMouseMoved(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseWheel then
OnMouseWheel(input, processed)
end
end)
InputEndedConn = UserInputService.InputEnded:connect(function(input, processed)
if input.UserInputType == Enum.UserInputType.Touch then
OnTouchEnded(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
OnMouse2Up(input, processed)
elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
OnMouse3Up(input, processed)
end
-- Keyboard
if input.UserInputType == Enum.UserInputType.Keyboard then
OnKeyUp(input, processed)
end
end)
MenuOpenedConn = GuiService.MenuOpened:connect(function()
this:ResetInputStates()
end)
workspaceChangedConn = workspace.Changed:connect(function(prop)
if prop == "CurrentCamera" then
onCurrentCameraChanged()
end
end)
onCurrentCameraChanged()
ShiftLockToggleConn = ShiftLockController.OnShiftLockToggled.Event:connect(function()
this:UpdateMouseBehavior()
end)
this.RotateInput = Vector2.new()
local activateGamepad = nil
local function assignActivateGamepad()
local connectedGamepads = UserInputService:GetConnectedGamepads()
if #connectedGamepads > 0 then
for i = 1, #connectedGamepads do
if activateGamepad == nil then
activateGamepad = connectedGamepads[i]
elseif connectedGamepads[i].Value < activateGamepad.Value then
activateGamepad = connectedGamepads[i]
end
end
end
if activateGamepad == nil then -- nothing is connected, at least set up for gamepad1
activateGamepad = Enum.UserInputType.Gamepad1
end
end
GamepadConnectedConn = UserInputService.GamepadDisconnected:connect(function(gamepadEnum)
if activateGamepad ~= gamepadEnum then return end
activateGamepad = nil
assignActivateGamepad()
end)
GamepadDisconnectedConn = UserInputService.GamepadConnected:connect(function(gamepadEnum)
if activateGamepad == nil then
assignActivateGamepad()
end
end)
local getGamepadPan = function(name, state, input)
if input.UserInputType == activateGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
if state == Enum.UserInputState.Cancel then
this.GamepadPanningCamera = Vector2.new(0,0)
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 = Vector2.new(0,0)
end
end
end
local doGamepadZoom = function(name, state, input)
if input.UserInputType == activateGamepad and input.KeyCode == Enum.KeyCode.ButtonR3 and state == Enum.UserInputState.Begin then
if this.ZoomEnabled then
if this.currentZoom > 0.5 then
this:ZoomCamera(0)
else
this:ZoomCamera(10)
end
end
end
end
game.ContextActionService:BindAction("RootCamGamepadPan", getGamepadPan, false, Enum.KeyCode.Thumbstick2)
game.ContextActionService:BindAction("RootCamGamepadZoom", doGamepadZoom, false, Enum.KeyCode.ButtonR3)
assignActivateGamepad()
-- set mouse behavior
self:UpdateMouseBehavior()
end
function this:SetEnabled(newState)
if newState ~= self.Enabled then
self.Enabled = newState
if self.Enabled then
self:ConnectInputEvents()
self.cframe = workspace.CurrentCamera.CoordinateFrame
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)
this:ZoomCamera(12.5)
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
local function setLookBehindCharacter()
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
end
wait()
setLookBehindCharacter()
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)
return this
end
return CreateCamera
]]>
-
AttachCamera
1 then
module:ResetCameraLook()
self.LastCameraTransform = nil
end
local subjectPosition = self:GetSubjectPosition()
if subjectPosition and player and camera then
local zoom = self:GetCameraZoom()
if zoom <= 0 then
zoom = 0.1
end
local humanoid = self:GetHumanoid()
if lastUpdate and humanoid and humanoid.Torso then
-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
local delta = math.min(0.1, now - lastUpdate)
local gamepadRotation = self:UpdateGamepad()
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
local forwardVector = humanoid.Torso.CFrame.lookVector
local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
if IsFinite(y) then
-- Preserve vertical rotation from user input
self.RotateInput = Vector2.new(y, self.RotateInput.Y)
end
end
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
self.RotateInput = Vector2.new()
camera.Focus = CFrame.new(subjectPosition)
camera.CoordinateFrame = CFrame.new(camera.Focus.p - (zoom * newLookVector), camera.Focus.p)
self.LastCameraTransform = camera.CoordinateFrame
end
lastUpdate = now
end
return module
end
return CreateAttachCamera
]]>
-
ClassicCamera
1 then
module:ResetCameraLook()
self.LastCameraTransform = nil
end
if lastUpdate then
local gamepadRotation = self:UpdateGamepad()
if self:ShouldUseVRRotation() then
self.RotateInput = self.RotateInput + self:GetVRRotationInput()
else
-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
local delta = math.min(0.1, now - lastUpdate)
if gamepadRotation ~= ZERO_VECTOR2 then
userPanningTheCamera = true
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
end
local angle = 0
if not (isInVehicle or isOnASkateboard) then
angle = angle + (self.TurningLeft and -120 or 0)
angle = angle + (self.TurningRight and 120 or 0)
end
if angle ~= 0 then
self.RotateInput = self.RotateInput + Vector2.new(math.rad(angle * delta), 0)
userPanningTheCamera = true
end
end
end
-- Reset tween speed if user is panning
if userPanningTheCamera then
tweenSpeed = 0
module.LastUserPanCamera = tick()
end
local userRecentlyPannedCamera = now - module.LastUserPanCamera < timeBeforeAutoRotate
local subjectPosition = self:GetSubjectPosition()
if subjectPosition and player and camera then
local zoom = self:GetCameraZoom()
if zoom < 0.5 then
zoom = 0.5
end
if self:GetShiftLock() and not self:IsInFirstPerson() then
-- We need to use the right vector of the camera after rotation, not before
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
local offset = ((newLookVector * XZ_VECTOR):Cross(UP_VECTOR).unit * 1.75)
if IsFiniteVector3(offset) then
subjectPosition = subjectPosition + offset
end
else
if self.LastCameraTransform and not userPanningTheCamera then
local isInFirstPerson = self:IsInFirstPerson()
if (isInVehicle or isOnASkateboard) and lastUpdate and humanoid and humanoid.Torso then
if isInFirstPerson then
if self.LastSubjectCFrame and (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
local y = -findAngleBetweenXZVectors(self.LastSubjectCFrame.lookVector, cameraSubject.CFrame.lookVector)
if IsFinite(y) then
self.RotateInput = self.RotateInput + Vector2.new(y, 0)
end
tweenSpeed = 0
end
elseif not userRecentlyPannedCamera then
local forwardVector = humanoid.Torso.CFrame.lookVector
if isOnASkateboard then
forwardVector = cameraSubject.CFrame.lookVector
end
tweenSpeed = clamp(0, tweenMaxSpeed, tweenSpeed + tweenAcceleration * timeDelta)
local percent = clamp(0, 1, tweenSpeed * timeDelta)
if self:IsInFirstPerson() then
percent = 1
end
local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
if IsFinite(y) and math.abs(y) > 0.0001 then
self.RotateInput = self.RotateInput + Vector2.new(y * percent, 0)
end
end
end
end
end
camera.Focus = VRService.VREnabled and self:GetVRFocus(subjectPosition, timeDelta) or CFrame.new(subjectPosition)
local cameraHeight = self:GetCameraHeight()
local cameraFocusP = camera.Focus.p
if VRService.VREnabled and not self:IsInFirstPerson() then
local vecToSubject = (subjectPosition - camera.CFrame.p)
local distToSubject = vecToSubject.magnitude
--Only move the camera if it exceeded a maximum distance to the subject in VR
if distToSubject > zoom or self.RotateInput.x ~= 0 then
local desiredDist = math.min(distToSubject, zoom)
vecToSubject = self:RotateCamera(vecToSubject.unit * Vector3.new(1, 0, 1), Vector2.new(self.RotateInput.x, 0)) * desiredDist
local newPos = cameraFocusP - vecToSubject
local desiredLookDir = camera.CFrame.lookVector
if self.RotateInput.x ~= 0 then
desiredLookDir = vecToSubject
end
local lookAt = Vector3.new(newPos.x + desiredLookDir.x, newPos.y, newPos.z + desiredLookDir.z)
self.RotateInput = ZERO_VECTOR2
camera.CFrame = CFrame.new(newPos, lookAt) + Vector3.new(0, cameraHeight, 0)
end
else
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
self.RotateInput = ZERO_VECTOR2
camera.CFrame = CFrame.new(cameraFocusP - (zoom * newLookVector), cameraFocusP) + Vector3.new(0, cameraHeight, 0)
end
self.LastCameraTransform = camera.CFrame
self.LastCameraFocus = camera.Focus
if isInVehicle or isOnASkateboard and cameraSubject:IsA('BasePart') then
self.LastSubjectCFrame = cameraSubject.CFrame
else
self.LastSubjectCFrame = nil
end
end
lastUpdate = now
end
return module
end
return CreateClassicCamera
]]>
-
FixedCamera
1 then
module:ResetCameraLook()
self.LastCameraTransform = nil
end
if lastUpdate then
-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
local delta = math.min(0.1, now - lastUpdate)
local gamepadRotation = self:UpdateGamepad()
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
end
local subjectPosition = self:GetSubjectPosition()
if subjectPosition and player and camera then
local zoom = self:GetCameraZoom()
if zoom <= 0 then
zoom = 0.1
end
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
self.RotateInput = Vector2.new()
camera.CoordinateFrame = CFrame.new(camera.Focus.p - (zoom * newLookVector), camera.Focus.p)
self.LastCameraTransform = camera.CoordinateFrame
end
lastUpdate = now
end
return module
end
return CreateFixedCamera
]]>
-
FollowCamera
1 then
module:ResetCameraLook()
self.LastCameraTransform = nil
end
if lastUpdate then
if self:ShouldUseVRRotation() then
self.RotateInput = self.RotateInput + self:GetVRRotationInput()
else
-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
local delta = math.min(0.1, now - lastUpdate)
local angle = 0
-- NOTE: Traditional follow camera does not rotate with arrow keys
if not (isInVehicle or isOnASkateboard) then
angle = angle + (self.TurningLeft and -120 or 0)
angle = angle + (self.TurningRight and 120 or 0)
end
local gamepadRotation = self:UpdateGamepad()
if gamepadRotation ~= Vector2.new(0,0) then
userPanningTheCamera = true
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
end
if angle ~= 0 then
userPanningTheCamera = true
self.RotateInput = self.RotateInput + Vector2.new(math.rad(angle * delta), 0)
end
end
end
-- Reset tween speed if user is panning
if userPanningTheCamera then
tweenSpeed = 0
module.LastUserPanCamera = tick()
end
local userRecentlyPannedCamera = now - module.LastUserPanCamera < timeBeforeAutoRotate
local subjectPosition = self:GetSubjectPosition()
if subjectPosition and player and camera then
local zoom = self:GetCameraZoom()
if zoom < 0.5 then
zoom = 0.5
end
if self:GetShiftLock() and not self:IsInFirstPerson() then
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
local offset = ((newLookVector * XZ_VECTOR):Cross(UP_VECTOR).unit * 1.75)
if IsFiniteVector3(offset) then
subjectPosition = subjectPosition + offset
end
else
if self.LastCameraTransform and not userPanningTheCamera then
local isInFirstPerson = self:IsInFirstPerson()
if (isClimbing or isInVehicle or isOnASkateboard) and lastUpdate and humanoid and humanoid.Torso then
if isInFirstPerson then
if self.LastSubjectCFrame and (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
local y = -findAngleBetweenXZVectors(self.LastSubjectCFrame.lookVector, cameraSubject.CFrame.lookVector)
if IsFinite(y) then
self.RotateInput = self.RotateInput + Vector2.new(y, 0)
end
tweenSpeed = 0
end
elseif not userRecentlyPannedCamera then
local forwardVector = humanoid.Torso.CFrame.lookVector
if isOnASkateboard then
forwardVector = cameraSubject.CFrame.lookVector
end
tweenSpeed = clamp(0, tweenMaxSpeed, tweenSpeed + tweenAcceleration * timeDelta)
local percent = clamp(0, 1, tweenSpeed * timeDelta)
if not isClimbing and self:IsInFirstPerson() then
percent = 1
end
local y = findAngleBetweenXZVectors(forwardVector, self:GetCameraLook())
-- Check for NaN
if IsFinite(y) and math.abs(y) > 0.0001 then
self.RotateInput = self.RotateInput + Vector2.new(y * percent, 0)
end
end
elseif not (isInFirstPerson or userRecentlyPannedCamera) and not VRService.VREnabled then
local lastVec = -(self.LastCameraTransform.p - subjectPosition)
local y = findAngleBetweenXZVectors(lastVec, self:GetCameraLook())
-- Check for NaNs
if IsFinite(y) and math.abs(y) > 0.0001 then
self.RotateInput = self.RotateInput + Vector2.new(y, 0)
end
end
end
end
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
self.RotateInput = Vector2.new()
camera.Focus = VRService.VREnabled and self:GetVRFocus(subjectPosition, timeDelta) or CFrame.new(subjectPosition)
camera.CFrame = CFrame.new(camera.Focus.p - (zoom * newLookVector), camera.Focus.p) + Vector3.new(0, self:GetCameraHeight(), 0)
self.LastCameraTransform = camera.CFrame
self.LastCameraFocus = camera.Focus
if isInVehicle or isOnASkateboard and cameraSubject:IsA('BasePart') then
self.LastSubjectCFrame = cameraSubject.CFrame
else
self.LastSubjectCFrame = nil
end
end
lastUpdate = now
end
return module
end
return CreateFollowCamera
]]>
-
ScriptableCamera
-
TrackCamera
1 then
module:ResetCameraLook()
self.LastCameraTransform = nil
end
if lastUpdate then
-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
local delta = math.min(0.1, now - lastUpdate)
local gamepadRotation = self:UpdateGamepad()
if gamepadRotation ~= Vector2.new(0,0) then
userPanningTheCamera = true
self.RotateInput = self.RotateInput + (gamepadRotation * delta)
end
end
local subjectPosition = self:GetSubjectPosition()
if subjectPosition and player and camera then
local zoom = self:GetCameraZoom()
if zoom <= 0 then
zoom = 0.1
end
local newLookVector = self:RotateCamera(self:GetCameraLook(), self.RotateInput)
self.RotateInput = Vector2.new()
camera.Focus = CFrame.new(subjectPosition)
camera.CoordinateFrame = CFrame.new(camera.Focus.p - (zoom * newLookVector), camera.Focus.p)
self.LastCameraTransform = camera.CoordinateFrame
end
lastUpdate = now
end
return module
end
return CreateTrackCamera
]]>
-
WatchCamera
1 then
module:ResetCameraLook()
self.LastCameraTransform = nil
self.LastZoom = nil
end
local subjectPosition = self:GetSubjectPosition()
if subjectPosition and player and camera then
local cameraLook = nil
if self.LastCameraTransform then
local humanoid = self:GetHumanoid()
if humanoid and humanoid.Torso then
-- TODO: let the paging buttons move the camera but not the mouse/touch
-- currently neither do
local diffVector = subjectPosition - self.LastCameraTransform.p
cameraLook = diffVector.unit
if self.LastZoom and self.LastZoom == self:GetCameraZoom() then
-- Don't clobber the zoom if they zoomed the camera
local zoom = diffVector.magnitude
self:ZoomCamera(zoom)
end
end
end
local zoom = self:GetCameraZoom()
if zoom <= 0 then
zoom = 0.1
end
local newLookVector = self:RotateVector(cameraLook or self:GetCameraLook(), self.RotateInput)
self.RotateInput = Vector2.new()
local newFocus = CFrame.new(subjectPosition)
local newCamCFrame = CFrame.new(newFocus.p - (zoom * newLookVector), newFocus.p)
camera.Focus = newFocus
camera.CoordinateFrame = newCamCFrame
self.LastCameraTransform = newCamCFrame
self.LastZoom = zoom
end
lastUpdate = now
end
return module
end
return CreateWatchCamera
]]>
-
ShiftLockController
-
TransparencyController