
KeylessUniversal

--[[
VERSIÓN MEJORADA Y CORREGIDA
]]
local Players = game:GetService("Players")
local CoreGui = game:GetService("CoreGui")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local Workspace = workspace
local StarterGui = game:GetService("StarterGui")
local LocalPlayer = Players.LocalPlayer
local Camera = Workspace.CurrentCamera
local Mouse = LocalPlayer:GetMouse()
if not Camera or not LocalPlayer or not Mouse then
return
end
local TWEEN_INFO_FAST = TweenInfo.new(0.20, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
local TWEEN_INFO_MED = TweenInfo.new(0.30, Enum.EasingStyle.Back, Enum.EasingDirection.Out)
local TWEEN_INFO_SLOW = TweenInfo.new(0.75, Enum.EasingStyle.Quart, Enum.EasingDirection.Out)
local THEME = {
bg = Color3.fromRGB(28, 28, 30),
panel = Color3.fromRGB(35, 35, 38),
miniPanel = Color3.fromRGB(15, 15, 17),
accent = Color3.fromRGB(0, 160, 255),
subtleAccent = Color3.fromRGB(16, 120, 200),
danger = Color3.fromRGB(255, 80, 80),
text = Color3.fromRGB(240, 240, 240),
muted = Color3.fromRGB(170, 170, 180)
}
local isMobile = UserInputService.TouchEnabled and not UserInputService.KeyboardEnabled
local scale = isMobile and 0.75 or 1.0
local SIZES = {
MainW = 320 * scale,
MainH = 460 * scale,
MainX = 60 * scale,
MainY = 80 * scale,
TitleH = 44 * scale,
BtnSize = 36 * scale,
BtnPadding = 10 * scale,
CornerS = 8 * scale,
CornerM = 10 * scale,
CornerL = 14 * scale,
SearchH = 36 * scale,
Padding = 14 * scale,
ListTop = 104 * scale,
ListBottom = 170 * scale,
BottomBtnH = 44 * scale,
BottomBtnY = 56 * scale,
EntryH = 56 * scale,
EntryPad = 12 * scale,
AvatarSize = 44 * scale,
AvatarPad = 10 * scale,
AvatarY = 6 * scale,
MiniH = 64 * scale,
MiniIconSize = 32 * scale,
MiniSideMargin = 12 * scale,
MiniBetweenPadding = 12 * scale,
MiniMinWidth = 120 * scale,
MiniMaxWidth = 420 * scale,
}
local FONTS = {
Title = math.floor(17 * scale),
Btn = math.floor(18 * scale),
MiniIcon = math.floor(18 * scale),
Search = math.floor(15 * scale),
State = math.floor(13 * scale),
Name = math.floor(15 * scale),
Sub = math.floor(13 * scale),
ViewBtn = math.floor(14 * scale),
BottomBtn = math.floor(16 * scale),
}
local existing = CoreGui:FindFirstChild("CamaraEspiaUI")
if existing then
existing:Destroy()
end
local isMinimized = false
local dragging, dragStart, startPos
local searchDebounce = false
local spyConnection = nil
local spyTargetPart = nil
local spiedPlayer = nil
local hiddenGuisBySpy = {}
local isSpying = false
local cameraCanClip = false
local touchId = nil
local orbit = {
enabled = false,
distance = 12,
minDistance = 1.2,
maxDistance = 60,
smoothing = 6,
}
local DEFAULT_ORBIT_MAX_DISTANCE = orbit.maxDistance
local isManualCameraActive = false
local manualJustActivated = false
local isTransitioning = false
local cameraAngle = Vector2.new(0, 0)
local orbitRadius = orbit.distance
local zoomDelta = 0
local isMouseDown = false
local CAMERA_ROTATION_SENSITIVITY = 0.015
local CAMERA_ZOOM_SPEED = 3
local lastTouchScale = nil
local PINCH_ZOOM_SENSITIVITY = 10
local isPlaceholderActive = false
local ctrlLockActive = false
local prevCameraSaved = false
local prevCameraOffset = nil
local prevCameraType = nil
local prevCameraSubject = nil
local initialCameraAngle = Vector2.new(0, 0)
local hasInitialAngle = false
local npcCache = {}
local lastNPCRefresh = 0
local NPC_CACHE_TIMEOUT = 2
local function getRootPart(character)
if not character then return nil end
return character:FindFirstChild("HumanoidRootPart") or
character:FindFirstChild("Torso") or
character:FindFirstChild("UpperTorso") or
character:FindFirstChild("Head")
end
local function safeName(player)
if not player then return "Jugador" end
local displayName = player.DisplayName and player.DisplayName ~= "" and player.DisplayName or player.Name
return displayName or "Jugador"
end
local function isPartPassable(part)
if not part or not part:IsA("BasePart") then return true end
if part.Anchored == false then return true end
if part.CanCollide == false then return true end
return false
end
local function getCameraPositionWithCollision(targetPos, desiredPos, ignoreInstances)
if cameraCanClip then return desiredPos, false end
local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Blacklist
rayParams.FilterDescendantsInstances = ignoreInstances or {}
local direction = desiredPos - targetPos
local ignored = {}
local attempts = 0
while true do
attempts = attempts + 1
local filterList = {}
if ignoreInstances then
for _, v in ipairs(ignoreInstances) do
table.insert(filterList, v)
end
end
for _, v in ipairs(ignored) do
table.insert(filterList, v)
end
rayParams.FilterDescendantsInstances = filterList
local result = Workspace:Raycast(targetPos, direction, rayParams)
if not result then
return desiredPos, false
end
local part = result.Instance
if isPartPassable(part) then
table.insert(ignored, part)
if attempts > 60 then
local offset = result.Normal * 0.5
return result.Position + offset, true
end
else
local offset = result.Normal * 0.5
return result.Position + offset, true
end
end
end
local function tweenGui(instance, props, info)
if instance and instance.Parent then
pcall(function()
TweenService:Create(instance, info or TWEEN_INFO_FAST, props):Play()
end)
end
end
local ScreenGui = Instance.new("ScreenGui")
ScreenGui.Name = "CamaraEspiaUI"
ScreenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
ScreenGui.Parent = CoreGui
ScreenGui.ResetOnSpawn = false
ScreenGui.Enabled = true
local SpyFadeOverlay = Instance.new("Frame", ScreenGui)
SpyFadeOverlay.Name = "SpyFadeOverlay"
SpyFadeOverlay.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
SpyFadeOverlay.BackgroundTransparency = 1
SpyFadeOverlay.BorderSizePixel = 0
SpyFadeOverlay.Size = UDim2.new(1, 0, 1, 0)
SpyFadeOverlay.ZIndex = 3
SpyFadeOverlay.Visible = false
local MainFrame = Instance.new("Frame", ScreenGui)
MainFrame.Name = "MainFrame"
MainFrame.Size = UDim2.new(0, SIZES.MainW, 0, SIZES.MainH)
MainFrame.Position = UDim2.new(0, SIZES.MainX, 0, SIZES.MainY)
MainFrame.BackgroundColor3 = THEME.bg
MainFrame.BorderSizePixel = 0
MainFrame.ClipsDescendants = true
MainFrame.Active = true
MainFrame.ZIndex = 2
Instance.new("UICorner", MainFrame).CornerRadius = UDim.new(0, SIZES.CornerL)
local mainStroke = Instance.new("UIStroke", MainFrame)
mainStroke.Color = Color3.fromRGB(40, 40, 45)
mainStroke.Transparency = 0.6
local MiniCover = Instance.new("Frame", MainFrame)
MiniCover.Name = "MiniCover"
MiniCover.Size = UDim2.new(1, 0, 1, 0)
MiniCover.Position = UDim2.new(0, 0, 0, 0)
MiniCover.BackgroundColor3 = THEME.miniPanel
MiniCover.BorderSizePixel = 0
MiniCover.Visible = false
Instance.new("UICorner", MiniCover).CornerRadius = UDim.new(0, SIZES.CornerL)
MiniCover.ZIndex = 1
local TitleBar = Instance.new("Frame", MainFrame)
TitleBar.Name = "TitleBar"
TitleBar.Size = UDim2.new(1, 0, 0, SIZES.TitleH)
TitleBar.Position = UDim2.new(0, 0, 0, 0)
TitleBar.BackgroundColor3 = THEME.panel
TitleBar.Active = true
Instance.new("UICorner", TitleBar).CornerRadius = UDim.new(0, SIZES.CornerL)
local TitleContainer = Instance.new("Frame", TitleBar)
TitleContainer.Size = UDim2.new(1, -(SIZES.Padding), 1, 0)
TitleContainer.Position = UDim2.new(0, SIZES.Padding / 2, 0, 0)
TitleContainer.BackgroundTransparency = 1
local UIListLayout_Title = Instance.new("UIListLayout", TitleContainer)
UIListLayout_Title.FillDirection = Enum.FillDirection.Horizontal
UIListLayout_Title.VerticalAlignment = Enum.VerticalAlignment.Center
UIListLayout_Title.HorizontalAlignment = Enum.HorizontalAlignment.Right
UIListLayout_Title.SortOrder = Enum.SortOrder.LayoutOrder
UIListLayout_Title.Padding = UDim.new(0, SIZES.BtnPadding)
local TitleLabel = Instance.new("TextLabel", TitleContainer)
TitleLabel.BackgroundTransparency = 1
TitleLabel.Size = UDim2.new(1, -(SIZES.BtnSize * 2 + SIZES.BtnPadding * 2), 1, 0)
TitleLabel.Text = "Cámara Espía"
TitleLabel.TextColor3 = THEME.text
TitleLabel.Font = Enum.Font.GothamBold
TitleLabel.TextSize = FONTS.Title
TitleLabel.TextXAlignment = Enum.TextXAlignment.Left
TitleLabel.LayoutOrder = 1
local ToggleCameraButton = Instance.new("TextButton", TitleContainer)
ToggleCameraButton.Name = "ToggleCameraButton"
ToggleCameraButton.Size = UDim2.new(0, SIZES.BtnSize, 0, SIZES.BtnSize)
ToggleCameraButton.BackgroundColor3 = THEME.subtleAccent
ToggleCameraButton.Text = "🔒"
ToggleCameraButton.Font = Enum.Font.GothamBold
ToggleCameraButton.TextSize = FONTS.Btn * 1.2
ToggleCameraButton.BorderSizePixel = 0
ToggleCameraButton.Visible = false
ToggleCameraButton.LayoutOrder = 2
Instance.new("UICorner", ToggleCameraButton).CornerRadius = UDim.new(0, SIZES.CornerS)
local MaximizeButton = Instance.new("TextButton", TitleContainer)
MaximizeButton.Size = UDim2.new(0, SIZES.BtnSize, 0, SIZES.BtnSize)
MaximizeButton.BackgroundColor3 = THEME.accent
MaximizeButton.Text = "—"
MaximizeButton.Font = Enum.Font.GothamBold
MaximizeButton.TextSize = FONTS.Btn
MaximizeButton.BorderSizePixel = 0
MaximizeButton.LayoutOrder = 3
Instance.new("UICorner", MaximizeButton).CornerRadius = UDim.new(0, SIZES.CornerS)
local MiniControls = Instance.new("Frame", MainFrame)
MiniControls.Name = "MiniControls"
MiniControls.BackgroundTransparency = 1
MiniControls.Visible = false
MiniControls.ClipsDescendants = false
MiniControls.ZIndex = 4
local MiniLayout = Instance.new("UIListLayout", MiniControls)
MiniLayout.FillDirection = Enum.FillDirection.Horizontal
MiniLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
MiniLayout.VerticalAlignment = Enum.VerticalAlignment.Center
MiniLayout.SortOrder = Enum.SortOrder.LayoutOrder
MiniLayout.Padding = UDim.new(0, SIZES.MiniBetweenPadding)
local MaximizeMiniButton = Instance.new("TextButton", MiniControls)
MaximizeMiniButton.Size = UDim2.new(0, SIZES.MiniIconSize, 0, SIZES.MiniIconSize)
MaximizeMiniButton.BackgroundColor3 = THEME.accent
MaximizeMiniButton.Text = "+"
MaximizeMiniButton.Font = Enum.Font.GothamBold
MaximizeMiniButton.TextSize = FONTS.MiniIcon
MaximizeMiniButton.TextColor3 = Color3.fromRGB(255, 255, 255)
MaximizeMiniButton.LayoutOrder = 1
Instance.new("UICorner", MaximizeMiniButton).CornerRadius = UDim.new(0, SIZES.CornerM)
MaximizeMiniButton.ZIndex = 5
local MiniManualButton = Instance.new("TextButton", MiniControls)
MiniManualButton.Size = UDim2.new(0, SIZES.MiniIconSize, 0, SIZES.MiniIconSize)
MiniManualButton.BackgroundColor3 = THEME.subtleAccent
MiniManualButton.Text = "🔒"
MiniManualButton.Font = Enum.Font.GothamBold
MiniManualButton.TextSize = FONTS.MiniIcon
MiniManualButton.TextColor3 = Color3.fromRGB(255, 255, 255)
MiniManualButton.LayoutOrder = 2
MiniManualButton.BorderSizePixel = 0
Instance.new("UICorner", MiniManualButton).CornerRadius = UDim.new(0, SIZES.CornerM)
MiniManualButton.ZIndex = 5
local MiniBackButton = Instance.new("TextButton", MiniControls)
MiniBackButton.Size = UDim2.new(0, SIZES.MiniIconSize, 0, SIZES.MiniIconSize)
MiniBackButton.BackgroundColor3 = THEME.danger
MiniBackButton.Text = ""
MiniBackButton.Font = Enum.Font.GothamBold
MiniBackButton.TextSize = FONTS.MiniIcon
MiniBackButton.TextColor3 = Color3.fromRGB(255, 255, 255)
MiniBackButton.LayoutOrder = 3
MiniBackButton.BorderSizePixel = 0
Instance.new("UICorner", MiniBackButton).CornerRadius = UDim.new(0, SIZES.CornerM)
MiniBackButton.ZIndex = 5
local MiniBackSquare = Instance.new("Frame", MiniBackButton)
local sqSize = SIZES.MiniIconSize - 26
MiniBackSquare.Size = UDim2.new(0, sqSize, 0, sqSize)
MiniBackSquare.Position = UDim2.new(0.5, -sqSize/2, 0.5, -sqSize/2)
MiniBackSquare.BackgroundTransparency = 1
MiniBackSquare.BorderSizePixel = 0
MiniBackSquare.ZIndex = 6
local MiniBackStroke = Instance.new("UIStroke", MiniBackSquare)
MiniBackStroke.Color = Color3.fromRGB(255, 255, 255)
MiniBackStroke.Thickness = 2.5
MiniBackStroke.ApplyStrokeMode = Enum.ApplyStrokeMode.Border
local SearchBox = Instance.new("TextBox", MainFrame)
SearchBox.Size = UDim2.new(1, -(SIZES.Padding * 2), 0, SIZES.SearchH)
SearchBox.Position = UDim2.new(0, SIZES.Padding, 0, SIZES.TitleH + 12 * scale)
SearchBox.PlaceholderText = "Buscar jugador o NPCs..."
SearchBox.PlaceholderColor3 = THEME.muted
SearchBox.BackgroundColor3 = Color3.fromRGB(38, 38, 40)
SearchBox.TextColor3 = THEME.text
SearchBox.TextSize = FONTS.Search
SearchBox.Text = ""
SearchBox.ClearTextOnFocus = false
Instance.new("UICorner", SearchBox).CornerRadius = UDim.new(0, SIZES.CornerS)
local PlayerFrame = Instance.new("ScrollingFrame", MainFrame)
PlayerFrame.Size = UDim2.new(1, -(SIZES.Padding * 2), 1, -SIZES.ListBottom)
PlayerFrame.Position = UDim2.new(0, SIZES.Padding, 0, SIZES.ListTop)
PlayerFrame.BackgroundColor3 = Color3.fromRGB(28, 28, 30)
PlayerFrame.ScrollBarThickness = 6 * scale
PlayerFrame.AutomaticCanvasSize = Enum.AutomaticSize.Y
PlayerFrame.ScrollBarImageColor3 = THEME.accent
PlayerFrame.ClipsDescendants = true
PlayerFrame.ScrollingDirection = Enum.ScrollingDirection.Y
Instance.new("UICorner", PlayerFrame).CornerRadius = UDim.new(0, 6 * scale)
local PlayerLayout = Instance.new("UIListLayout", PlayerFrame)
PlayerLayout.Padding = UDim.new(0, SIZES.CornerS)
PlayerLayout.SortOrder = Enum.SortOrder.LayoutOrder
PlayerLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
local BackToPlayerButton = Instance.new("TextButton", MainFrame)
BackToPlayerButton.Size = UDim2.new(1, -(SIZES.Padding * 2), 0, SIZES.BottomBtnH)
BackToPlayerButton.Position = UDim2.new(0, SIZES.Padding, 1, -SIZES.BottomBtnY)
BackToPlayerButton.BackgroundColor3 = THEME.danger
BackToPlayerButton.Text = "Volver a mi cámara"
BackToPlayerButton.Font = Enum.Font.GothamBold
BackToPlayerButton.TextSize = FONTS.BottomBtn
BackToPlayerButton.BorderSizePixel = 0
Instance.new("UICorner", BackToPlayerButton).CornerRadius = UDim.new(0, SIZES.CornerM)
local StateLabel = Instance.new("TextLabel", MainFrame)
StateLabel.Size = UDim2.new(1, -(SIZES.Padding * 2), 0, 20 * scale)
StateLabel.Position = UDim2.new(0, SIZES.Padding, 0, SIZES.TitleH + 12 * scale + SIZES.SearchH + 4 * scale)
StateLabel.BackgroundTransparency = 1
StateLabel.TextColor3 = THEME.muted
StateLabel.Font = Enum.Font.Gotham
StateLabel.TextSize = FONTS.State
StateLabel.Visible = false
local function cleanupSpyMode()
for gui, _ in pairs(hiddenGuisBySpy) do
if gui and gui.Parent then
pcall(function() gui.Enabled = true end)
end
end
hiddenGuisBySpy = {}
end
local function setSpyMode(enabled)
if enabled == isSpying then return end
isSpying = enabled
if enabled then
hiddenGuisBySpy = {}
cameraCanClip = false
pcall(function()
StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.All, false)
StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.PlayerList, false)
end)
for _, gui in ipairs(LocalPlayer.PlayerGui:GetChildren()) do
if gui:IsA("ScreenGui") and gui.Name ~= ScreenGui.Name and gui.Enabled == true then
hiddenGuisBySpy[gui] = true
gui.Enabled = false
end
end
else
cameraCanClip = false
pcall(function()
StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.All, true)
StarterGui:SetCoreGuiEnabled(Enum.CoreGuiType.PlayerList, true)
end)
cleanupSpyMode()
end
if not isMinimized then
ToggleCameraButton.Visible = isSpying
end
end
local function stopSpy()
if spyConnection then
spyConnection:Disconnect()
spyConnection = nil
end
isTransitioning = false
spyTargetPart = nil
spiedPlayer = nil
orbit.enabled = false
isMouseDown = false
lastTouchScale = nil
touchId = nil
ctrlLockActive = false
pcall(function()
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
pcall(function() UserInputService.MouseIconEnabled = true end)
end)
end
local function syncManualIcons()
local icon = isManualCameraActive and "🎮" or "🔒"
if ToggleCameraButton and ToggleCameraButton.Parent then
ToggleCameraButton.Text = icon
end
if MiniManualButton and MiniManualButton.Parent then
MiniManualButton.Text = icon
end
local color = isManualCameraActive and THEME.accent or THEME.subtleAccent
if ToggleCameraButton and ToggleCameraButton.Parent then
ToggleCameraButton.BackgroundColor3 = color
end
if MiniManualButton and MiniManualButton.Parent then
MiniManualButton.BackgroundColor3 = color
end
end
local function initManualAnglesFromCurrentCamera(targetPos)
local camPos = Camera.CFrame.Position
local toTarget = (camPos - targetPos)
local dir = toTarget.Unit
local rotY = math.atan2(dir.X, -dir.Z)
local rotX = math.asin(math.clamp(dir.Y, -0.999, 0.999))
cameraAngle = Vector2.new(rotY, rotX)
local rotation = CFrame.Angles(0, rotY, 0) * CFrame.Angles(rotX, 0, 0)
local correctPos = targetPos + (rotation.LookVector * orbitRadius * -1)
Camera.CFrame = CFrame.lookAt(correctPos, targetPos)
end
local function toggleManualCamera(enabled)
isManualCameraActive = enabled
syncManualIcons()
if enabled then
if spyTargetPart and spyTargetPart.Parent then
local tPos = spyTargetPart.Position + Vector3.new(0, 1.5, 0)
local realDist = (Camera.CFrame.Position - tPos).Magnitude
orbitRadius = math.clamp(realDist, orbit.minDistance, orbit.maxDistance)
initManualAnglesFromCurrentCamera(tPos)
else
orbitRadius = orbit.distance
end
orbit.maxDistance = math.max(DEFAULT_ORBIT_MAX_DISTANCE, 1000000)
lastTouchScale = nil
manualJustActivated = true
else
orbit.maxDistance = DEFAULT_ORBIT_MAX_DISTANCE
orbit.distance = orbitRadius
manualJustActivated = false
pcall(function()
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
pcall(function() UserInputService.MouseIconEnabled = true end)
end)
end
end
local function captureInitialCameraAngle()
if not hasInitialAngle then
local camPos = Camera.CFrame.Position
local forward = Camera.CFrame.LookVector
local rotY = math.atan2(forward.X, -forward.Z)
local rotX = math.asin(math.clamp(forward.Y, -0.999, 0.999))
initialCameraAngle = Vector2.new(rotY, rotX)
hasInitialAngle = true
end
end
local function startSpyOnCharacter(charModel)
if not charModel or not charModel.Parent then return end
if not isSpying then
captureInitialCameraAngle()
end
if not prevCameraSaved then
local localRoot = getRootPart(LocalPlayer.Character)
if localRoot then
prevCameraType = Camera.CameraType
prevCameraSubject = Camera.CameraSubject
prevCameraOffset = localRoot.CFrame:ToObjectSpace(Camera.CFrame)
prevCameraSaved = true
end
end
local savedRadius
if isManualCameraActive then
savedRadius = orbitRadius
elseif spyTargetPart and spyTargetPart.Parent then
local tPos = spyTargetPart.Position + Vector3.new(0, 1.5, 0)
savedRadius = (Camera.CFrame.Position - tPos).Magnitude
else
local localRoot = getRootPart(LocalPlayer.Character)
savedRadius = localRoot and (Camera.CFrame.Position - localRoot.Position).Magnitude or orbit.distance
end
savedRadius = math.clamp(savedRadius, orbit.minDistance, DEFAULT_ORBIT_MAX_DISTANCE)
stopSpy()
local root = getRootPart(charModel)
if not root then return end
spyTargetPart = root
spiedPlayer = Players:GetPlayerFromCharacter(charModel)
orbit.distance = savedRadius
orbitRadius = savedRadius
orbit.maxDistance = isManualCameraActive
and math.max(DEFAULT_ORBIT_MAX_DISTANCE, 1000000)
or DEFAULT_ORBIT_MAX_DISTANCE
orbit.enabled = true
Camera.CameraType = Enum.CameraType.Scriptable
setSpyMode(true)
if isManualCameraActive then
local tPos = root.Position + Vector3.new(0, 1.5, 0)
local startCF = Camera.CFrame
local duration = 1
local elapsed = 0
isTransitioning = true
local animConn
animConn = RunService.RenderStepped:Connect(function(dt)
pcall(function()
elapsed = elapsed + dt
local t = math.clamp(elapsed / duration, 0, 1)
local e = 1 - (1 - t)^3
local behindOffset = -root.CFrame.LookVector * orbitRadius + Vector3.new(0, 3, 0)
local desiredPos = tPos + behindOffset
local goalCF = CFrame.lookAt(desiredPos, tPos)
Camera.CFrame = startCF:Lerp(goalCF, e)
if elapsed >= duration then
if animConn then
animConn:Disconnect()
animConn = nil
end
isTransitioning = false
local finalDir = (Camera.CFrame.Position - tPos).Unit
cameraAngle = Vector2.new(
math.atan2(finalDir.X, -finalDir.Z),
math.asin(math.clamp(finalDir.Y, -0.999, 0.999))
)
initialCameraAngle = cameraAngle
end
end)
end)
end
spyConnection = RunService.RenderStepped:Connect(function(dt)
pcall(function()
if isTransitioning then return end
if not spyTargetPart or not spyTargetPart.Parent then
if spiedPlayer and spiedPlayer.Character then
local newRoot = getRootPart(spiedPlayer.Character)
if newRoot then
spyTargetPart = newRoot
else
resetCamera()
return
end
else
resetCamera()
return
end
end
local tPos = spyTargetPart.Position + Vector3.new(0, 1.5, 0)
local ignoreList = {}
if spyTargetPart.Parent then table.insert(ignoreList, spyTargetPart.Parent) end
if LocalPlayer and LocalPlayer.Character then table.insert(ignoreList, LocalPlayer.Character) end
local targetCFrame
if isManualCameraActive then
orbitRadius = math.max(orbit.minDistance, orbitRadius - zoomDelta)
zoomDelta = 0
local rotation = CFrame.Angles(0, cameraAngle.X, 0) * CFrame.Angles(cameraAngle.Y, 0, 0)
local desiredPos = tPos + (rotation.LookVector * orbitRadius * -1)
local camPosCollided, _ = getCameraPositionWithCollision(tPos, desiredPos, ignoreList)
targetCFrame = CFrame.lookAt(camPosCollided, tPos)
else
orbitRadius = orbit.distance
local desiredPos = tPos - spyTargetPart.CFrame.LookVector * orbit.distance + Vector3.new(0, 3, 0)
local minHeight = math.max(2.5, tPos.Y + 1)
if desiredPos.Y < minHeight then desiredPos = Vector3.new(desiredPos.X, minHeight, desiredPos.Z) end
local camPosCollided, _ = getCameraPositionWithCollision(tPos, desiredPos, ignoreList)
targetCFrame = CFrame.lookAt(camPosCollided, tPos)
end
if manualJustActivated then
Camera.CFrame = targetCFrame
manualJustActivated = false
else
local smooth = isManualCameraActive and 10 or orbit.smoothing
local alpha = math.clamp(1 - math.exp(-dt * smooth), 0, 1)
Camera.CFrame = Camera.CFrame:Lerp(targetCFrame, alpha)
end
end)
end)
end
function resetCamera()
if not isSpying then return end
local hadSpy = isSpying
if spyConnection then
spyConnection:Disconnect()
spyConnection = nil
end
local root = getRootPart(LocalPlayer.Character)
if prevCameraSaved and root and prevCameraOffset then
Camera.CameraType = Enum.CameraType.Scriptable
local startCFrame = Camera.CFrame
local duration = 1.1
local elapsed = 0
local resetConn
resetConn = RunService.RenderStepped:Connect(function(dt)
pcall(function()
elapsed = elapsed + dt
local t = math.clamp(elapsed / duration, 0, 1)
local eased
if t < 0.5 then
eased = 4 * t * t * t
else
local f = (2 * t) - 2
eased = 0.5 * f * f * f + 1
end
local currentRoot = getRootPart(LocalPlayer.Character)
if currentRoot then
local endCFrame = currentRoot.CFrame * prevCameraOffset
Camera.CFrame = startCFrame:Lerp(endCFrame, eased)
end
if elapsed >= duration then
if resetConn then
resetConn:Disconnect()
resetConn = nil
end
if prevCameraType then
Camera.CameraType = prevCameraType
else
Camera.CameraType = Enum.CameraType.Custom
end
if prevCameraType == Enum.CameraType.Custom and prevCameraSubject then
pcall(function() Camera.CameraSubject = prevCameraSubject end)
elseif prevCameraType == Enum.CameraType.Custom then
local humanoid = LocalPlayer.Character and LocalPlayer.Character:FindFirstChildOfClass("Humanoid")
if humanoid then pcall(function() Camera.CameraSubject = humanoid end) end
end
prevCameraSaved = false
prevCameraOffset = nil
prevCameraType = nil
prevCameraSubject = nil
end
end)
end)
elseif root then
Camera.CameraType = Enum.CameraType.Scriptable
local startCFrame = Camera.CFrame
local duration = 1.1
local elapsed = 0
local resetConn
resetConn = RunService.RenderStepped:Connect(function(dt)
pcall(function()
elapsed = elapsed + dt
local t = math.clamp(elapsed / duration, 0, 1)
local eased
if t < 0.5 then
eased = 4 * t * t * t
else
local f = (2 * t) - 2
eased = 0.5 * f * f * f + 1
end
local currentRoot = getRootPart(LocalPlayer.Character)
if currentRoot then
local desiredPos = currentRoot.Position + currentRoot.CFrame.LookVector * -6 + Vector3.new(0, 3, 0)
local endCFrame = CFrame.lookAt(desiredPos, currentRoot.Position)
Camera.CFrame = startCFrame:Lerp(endCFrame, eased)
end
if elapsed >= duration then
if resetConn then
resetConn:Disconnect()
resetConn = nil
end
Camera.CameraType = Enum.CameraType.Custom
local humanoid = LocalPlayer.Character:FindFirstChildOfClass("Humanoid")
if humanoid then Camera.CameraSubject = humanoid end
end
end)
end)
else
Camera.CameraType = Enum.CameraType.Custom
end
if hadSpy then
setSpyMode(false)
end
stopSpy()
hasInitialAngle = false
initialCameraAngle = Vector2.new(0, 0)
orbit.maxDistance = DEFAULT_ORBIT_MAX_DISTANCE
syncManualIcons()
end
BackToPlayerButton.MouseButton1Click:Connect(function()
if not isSpying then return end
tweenGui(BackToPlayerButton, {BackgroundColor3 = THEME.subtleAccent}, TWEEN_INFO_FAST)
task.delay(0.12, function()
tweenGui(BackToPlayerButton, {BackgroundColor3 = THEME.danger}, TWEEN_INFO_FAST)
end)
resetCamera()
end)
MiniBackButton.MouseButton1Click:Connect(function()
if not isSpying then return end
tweenGui(MiniBackButton, {BackgroundTransparency = 0.5}, TWEEN_INFO_FAST)
task.delay(0.12, function()
tweenGui(MiniBackButton, {BackgroundTransparency = 0}, TWEEN_INFO_FAST)
end)
resetCamera()
end)
MiniManualButton.MouseButton1Click:Connect(function()
if not isSpying then return end
toggleManualCamera(not isManualCameraActive)
end)
ToggleCameraButton.MouseButton1Click:Connect(function()
if not isSpying then return end
toggleManualCamera(not isManualCameraActive)
end)
local function createNPCEntry(model)
local entry = Instance.new("TextButton")
entry.Name = "NpcBtn_" .. model:GetFullName()
entry.Parent = PlayerFrame
entry.Size = UDim2.new(1, -SIZES.EntryPad, 0, SIZES.EntryH)
entry.BackgroundColor3 = Color3.fromRGB(38, 38, 40)
entry.BorderSizePixel = 0
entry.AutoButtonColor = true
entry.Text = ""
entry.LayoutOrder = #PlayerFrame:GetChildren()
Instance.new("UICorner", entry).CornerRadius = UDim.new(0, SIZES.CornerM)
local left = Instance.new("Frame", entry)
left.BackgroundTransparency = 1
left.Position = UDim2.new(0, SIZES.AvatarPad, 0, SIZES.AvatarY)
left.Size = UDim2.new(0, SIZES.AvatarSize, 1, -(SIZES.AvatarY * 2))
local avatar = Instance.new("ImageLabel", left)
avatar.Size = UDim2.new(1, 0, 1, 0)
avatar.BackgroundColor3 = Color3.fromRGB(50, 50, 50)
avatar.BorderSizePixel = 0
avatar.Image = "rbxassetid://1322223555"
avatar.ScaleType = Enum.ScaleType.Crop
Instance.new("UICorner", avatar).CornerRadius = UDim.new(0, SIZES.CornerS)
local nameX = SIZES.AvatarPad + SIZES.AvatarSize + 10 * scale
local btnW = 90 * scale
local btnPadding = SIZES.AvatarPad
local btnTotalSpace = btnW + btnPadding
local nameLabel = Instance.new("TextLabel", entry)
nameLabel.BackgroundTransparency = 1
nameLabel.Position = UDim2.new(0, nameX, 0, 8 * scale)
nameLabel.Size = UDim2.new(1, -(nameX + btnTotalSpace + 5 * scale), 0, FONTS.Name + 5 * scale)
nameLabel.TextXAlignment = Enum.TextXAlignment.Left
nameLabel.Text = model.Name
nameLabel.TextColor3 = THEME.text
nameLabel.Font = Enum.Font.GothamSemibold
nameLabel.TextSize = FONTS.Name
nameLabel.TextTruncate = Enum.TextTruncate.AtEnd
local subLabel = Instance.new("TextLabel", entry)
subLabel.BackgroundTransparency = 1
subLabel.Position = UDim2.new(0, nameX, 0, (8 * scale) + FONTS.Name + (2 * scale))
subLabel.Size = UDim2.new(1, -(nameX + btnTotalSpace + 5 * scale), 0, FONTS.Sub + 5 * scale)
subLabel.TextXAlignment = Enum.TextXAlignment.Left
subLabel.Text = "NPC"
subLabel.TextColor3 = THEME.muted
subLabel.Font = Enum.Font.Gotham
subLabel.TextSize = FONTS.Sub
subLabel.TextTruncate = Enum.TextTruncate.AtEnd
local viewBtn = Instance.new("TextButton", entry)
viewBtn.Size = UDim2.new(0, btnW, 0, SIZES.BtnSize)
viewBtn.Position = UDim2.new(1, -(btnW + btnPadding), 0.5, -(SIZES.BtnSize / 2))
viewBtn.BackgroundColor3 = THEME.accent
viewBtn.Text = "Ver"
viewBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
viewBtn.Font = Enum.Font.GothamBold
viewBtn.TextSize = FONTS.ViewBtn
viewBtn.BorderSizePixel = 0
Instance.new("UICorner", viewBtn).CornerRadius = UDim.new(0, SIZES.CornerS)
entry.MouseEnter:Connect(function()
tweenGui(entry, {BackgroundColor3 = Color3.fromRGB(45, 45, 47)}, TWEEN_INFO_FAST)
end)
entry.MouseLeave:Connect(function()
tweenGui(entry, {BackgroundColor3 = Color3.fromRGB(38, 38, 40)}, TWEEN_INFO_FAST)
end)
viewBtn.MouseButton1Click:Connect(function()
tweenGui(viewBtn, {BackgroundTransparency = 0.5}, TWEEN_INFO_FAST)
task.delay(0.12, function()
tweenGui(viewBtn, {BackgroundTransparency = 0}, TWEEN_INFO_FAST)
end)
startSpyOnCharacter(model)
end)
return entry
end
local function createPlayerEntry(player)
local entry = Instance.new("TextButton")
entry.Name = "PlayerBtn_" .. player.UserId
entry.Parent = PlayerFrame
entry.Size = UDim2.new(1, -SIZES.EntryPad, 0, SIZES.EntryH)
entry.BackgroundColor3 = Color3.fromRGB(38, 38, 40)
entry.BorderSizePixel = 0
entry.AutoButtonColor = true
entry.Text = ""
entry.LayoutOrder = #PlayerFrame:GetChildren()
Instance.new("UICorner", entry).CornerRadius = UDim.new(0, SIZES.CornerM)
local left = Instance.new("Frame", entry)
left.BackgroundTransparency = 1
left.Position = UDim2.new(0, SIZES.AvatarPad, 0, SIZES.AvatarY)
left.Size = UDim2.new(0, SIZES.AvatarSize, 1, -(SIZES.AvatarY * 2))
local avatar = Instance.new("ImageLabel", left)
avatar.Size = UDim2.new(1, 0, 1, 0)
avatar.BackgroundColor3 = Color3.fromRGB(50, 50, 50)
avatar.BorderSizePixel = 0
avatar.Image = "rbxthumb://type=AvatarHeadShot&id=" .. tostring(player.UserId) .. "&w=150&h=150"
avatar.ScaleType = Enum.ScaleType.Crop
Instance.new("UICorner", avatar).CornerRadius = UDim.new(0, SIZES.CornerS)
local nameX = SIZES.AvatarPad + SIZES.AvatarSize + 10 * scale
local btnW = 90 * scale
local btnPadding = SIZES.AvatarPad
local btnTotalSpace = btnW + btnPadding
local nameLabel = Instance.new("TextLabel", entry)
nameLabel.BackgroundTransparency = 1
nameLabel.Position = UDim2.new(0, nameX, 0, 8 * scale)
nameLabel.Size = UDim2.new(1, -(nameX + btnTotalSpace + 5 * scale), 0, FONTS.Name + 5 * scale)
nameLabel.TextXAlignment = Enum.TextXAlignment.Left
nameLabel.Text = safeName(player)
nameLabel.TextColor3 = THEME.text
nameLabel.Font = Enum.Font.GothamSemibold
nameLabel.TextSize = FONTS.Name
nameLabel.TextTruncate = Enum.TextTruncate.AtEnd
local subLabel = Instance.new("TextLabel", entry)
subLabel.BackgroundTransparency = 1
subLabel.Position = UDim2.new(0, nameX, 0, (8 * scale) + FONTS.Name + (2 * scale))
subLabel.Size = UDim2.new(1, -(nameX + btnTotalSpace + 5 * scale), 0, FONTS.Sub + 5 * scale)
subLabel.TextXAlignment = Enum.TextXAlignment.Left
subLabel.Text = "@" .. player.Name
subLabel.TextColor3 = THEME.muted
subLabel.Font = Enum.Font.Gotham
subLabel.TextSize = FONTS.Sub
subLabel.TextTruncate = Enum.TextTruncate.AtEnd
local viewBtn = Instance.new("TextButton", entry)
viewBtn.Size = UDim2.new(0, btnW, 0, SIZES.BtnSize)
viewBtn.Position = UDim2.new(1, -(btnW + btnPadding), 0.5, -(SIZES.BtnSize / 2))
viewBtn.BackgroundColor3 = THEME.accent
viewBtn.Text = "Ver"
viewBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
viewBtn.Font = Enum.Font.GothamBold
viewBtn.TextSize = FONTS.ViewBtn
viewBtn.BorderSizePixel = 0
Instance.new("UICorner", viewBtn).CornerRadius = UDim.new(0, SIZES.CornerS)
entry.MouseEnter:Connect(function()
tweenGui(entry, {BackgroundColor3 = Color3.fromRGB(45, 45, 47)}, TWEEN_INFO_FAST)
end)
entry.MouseLeave:Connect(function()
tweenGui(entry, {BackgroundColor3 = Color3.fromRGB(38, 38, 40)}, TWEEN_INFO_FAST)
end)
viewBtn.MouseButton1Click:Connect(function()
tweenGui(viewBtn, {BackgroundTransparency = 0.5}, TWEEN_INFO_FAST)
task.delay(0.12, function()
tweenGui(viewBtn, {BackgroundTransparency = 0}, TWEEN_INFO_FAST)
end)
startSpyOnCharacter(player.Character)
end)
return entry
end
local function refreshPlayers(filter)
filter = (filter or ""):lower()
for _, child in ipairs(PlayerFrame:GetChildren()) do
if child:IsA("TextButton") and (child.Name:match("^PlayerBtn_") or child.Name:match("^NpcBtn_")) then
child:Destroy()
end
end
local visibleCount = 0
for _, player in ipairs(Players:GetPlayers()) do
if player == LocalPlayer then continue end
local display = (player.DisplayName or player.Name):lower()
local username = (player.Name or ""):lower()
if filter == "" or display:find(filter, 1, true) or username:find(filter, 1, true) then
visibleCount = visibleCount + 1
local entry = createPlayerEntry(player)
entry.LayoutOrder = visibleCount
entry.BackgroundTransparency = 1
tweenGui(entry, {BackgroundTransparency = 0}, TWEEN_INFO_MED)
end
end
local npcCount = 0
local processedNPCs = {}
local currentTime = tick()
if currentTime - lastNPCRefresh > NPC_CACHE_TIMEOUT then
npcCache = {}
lastNPCRefresh = currentTime
end
for _, descendant in ipairs(Workspace:GetDescendants()) do
if descendant:IsA("Humanoid") then
local model = descendant.Parent
if not processedNPCs[model] then
local rootPart = model:FindFirstChild("HumanoidRootPart")
if rootPart and descendant.Health > 0 and not Players:GetPlayerFromCharacter(model) then
local npcName = model.Name:lower()
if filter == "" or npcName:find(filter, 1, true) then
processedNPCs[model] = true
npcCount = npcCount + 1
local entry = createNPCEntry(model)
entry.LayoutOrder = visibleCount + npcCount + 1000
entry.BackgroundTransparency = 1
tweenGui(entry, {BackgroundTransparency = 0}, TWEEN_INFO_MED)
end
end
end
end
end
if visibleCount == 0 and npcCount == 0 then
StateLabel.Text = "No se encontraron resultados"
StateLabel.Visible = true
tweenGui(StateLabel, {TextTransparency = 0}, TWEEN_INFO_FAST)
else
tweenGui(StateLabel, {TextTransparency = 1}, TWEEN_INFO_FAST)
task.delay(0.25, function() StateLabel.Visible = false end)
end
end
PlayerLayout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function()
local y = PlayerLayout.AbsoluteContentSize.Y
PlayerFrame.CanvasSize = UDim2.new(0, 0, 0, y + 10 * scale)
end)
Players.PlayerAdded:Connect(function(player)
local filterText = isPlaceholderActive and "" or SearchBox.Text
refreshPlayers(filterText)
end)
Players.PlayerRemoving:Connect(function(player)
if spiedPlayer and player == spiedPlayer then resetCamera() end
local filterText = isPlaceholderActive and "" or SearchBox.Text
refreshPlayers(filterText)
end)
SearchBox:GetPropertyChangedSignal("Text"):Connect(function()
if SearchBox.Text ~= SearchBox.PlaceholderText and SearchBox.Text ~= "" then
isPlaceholderActive = false
end
if searchDebounce then return end
searchDebounce = true
task.delay(0.12, function()
local filterText = (SearchBox.Text == SearchBox.PlaceholderText or SearchBox.Text == "") and "" or SearchBox.Text
refreshPlayers(filterText)
searchDebounce = false
end)
end)
SearchBox.Focused:Connect(function()
if isPlaceholderActive then
SearchBox.Text = ""
end
end)
SearchBox.FocusLost:Connect(function()
if SearchBox.Text == "" then
isPlaceholderActive = true
SearchBox.Text = SearchBox.PlaceholderText
refreshPlayers("")
end
end)
refreshPlayers("")
local function computeMiniWidth()
local children = MiniControls:GetChildren()
local btnCount = 0
for _, c in ipairs(children) do
if c:IsA("GuiObject") and c.Visible then
btnCount = btnCount + 1
end
end
if btnCount == 0 then return SIZES.MiniMinWidth end
local totalIconsWidth = btnCount * SIZES.MiniIconSize
local totalPadding = math.max(0, btnCount - 1) * SIZES.MiniBetweenPadding
local width = SIZES.MiniSideMargin * 2 + totalIconsWidth + totalPadding
width = math.clamp(width, SIZES.MiniMinWidth, SIZES.MiniMaxWidth)
return width
end
local function updateMiniButtons()
if isMinimized then
MiniManualButton.Visible = isSpying
else
MiniManualButton.Visible = false
end
MaximizeMiniButton.Visible = true
MiniBackButton.Visible = true
syncManualIcons()
if isMinimized then
local newW = computeMiniWidth()
local miniInnerW = newW - (SIZES.MiniSideMargin * 2)
tweenGui(MiniControls, {Size = UDim2.new(0, miniInnerW, 0, SIZES.MiniH)}, TWEEN_INFO_MED)
tweenGui(MiniControls, {Position = UDim2.new(0.5, -miniInnerW / 2, 0.5, -(SIZES.MiniH / 2))}, TWEEN_INFO_MED)
tweenGui(MainFrame, {Size = UDim2.new(0, newW, 0, SIZES.MiniH)}, TWEEN_INFO_MED)
end
end
spawn(function()
while true do
updateMiniButtons()
task.wait(0.35)
end
end)
local function hideContentNow()
SearchBox.Visible = false
PlayerFrame.Visible = false
BackToPlayerButton.Visible = false
StateLabel.Visible = false
TitleLabel.Visible = false
end
local function showContentNow()
SearchBox.Visible = true
PlayerFrame.Visible = true
BackToPlayerButton.Visible = true
TitleLabel.Visible = true
end
local function minimizeUI()
if isMinimized then return end
isMinimized = true
hideContentNow()
MiniCover.Visible = true
MiniCover.BackgroundTransparency = 0
MiniCover.ZIndex = 2
tweenGui(TitleBar, {BackgroundColor3 = THEME.miniPanel}, TWEEN_INFO_FAST)
tweenGui(TitleLabel, {TextTransparency = 1}, TWEEN_INFO_FAST)
tweenGui(SearchBox, {BackgroundTransparency = 1, TextTransparency = 1}, TWEEN_INFO_FAST)
tweenGui(PlayerFrame, {BackgroundTransparency = 1}, TWEEN_INFO_FAST)
tweenGui(BackToPlayerButton, {BackgroundTransparency = 1, TextTransparency = 1}, TWEEN_INFO_FAST)
updateMiniButtons()
task.delay(0.22, function()
MaximizeButton.Visible = false
ToggleCameraButton.Visible = false
MiniControls.Visible = true
MiniControls.ZIndex = 4
MiniCover.ZIndex = 1
end)
end
local function maximizeUI()
if not isMinimized then return end
isMinimized = false
showContentNow()
MiniCover.Visible = false
TitleLabel.TextTransparency = 1
SearchBox.BackgroundTransparency = 1
SearchBox.TextTransparency = 1
PlayerFrame.BackgroundTransparency = 1
BackToPlayerButton.BackgroundTransparency = 1
BackToPlayerButton.TextTransparency = 1
tweenGui(MainFrame, {Size = UDim2.new(0, SIZES.MainW, 0, SIZES.MainH)}, TWEEN_INFO_MED)
MaximizeButton.Visible = true
ToggleCameraButton.Visible = isSpying
MiniControls.Visible = false
tweenGui(TitleBar, {BackgroundColor3 = THEME.panel}, TWEEN_INFO_FAST)
tweenGui(TitleLabel, {TextTransparency = 0}, TWEEN_INFO_FAST)
tweenGui(SearchBox, {BackgroundTransparency = 0, TextTransparency = 0}, TWEEN_INFO_FAST)
tweenGui(PlayerFrame, {BackgroundTransparency = 0}, TWEEN_INFO_FAST)
tweenGui(BackToPlayerButton, {BackgroundTransparency = 0, TextTransparency = 0}, TWEEN_INFO_FAST)
end
MaximizeButton.MouseButton1Click:Connect(function()
if not isMinimized then
minimizeUI()
MaximizeButton.Text = "+"
else
maximizeUI()
MaximizeButton.Text = "—"
end
end)
MaximizeMiniButton.MouseButton1Click:Connect(function()
maximizeUI()
MaximizeButton.Text = "—"
end)
local function beginDrag(input)
if input.UserInputType ~= Enum.UserInputType.MouseButton1 and input.UserInputType ~= Enum.UserInputType.Touch then
return
end
dragging = true
dragStart = input.Position
startPos = MainFrame.Position
input.Changed:Connect(function()
if input.UserInputState == Enum.UserInputState.End then
dragging = false
end
end)
end
local function updateDrag(input)
if not dragging then return end
local delta = input.Position - dragStart
local newX = startPos.X.Offset + delta.X
local newY = startPos.Y.Offset + delta.Y
local screenW, screenH = Workspace.CurrentCamera.ViewportSize.X, Workspace.CurrentCamera.ViewportSize.Y
local fw, fh = MainFrame.AbsoluteSize.X, MainFrame.AbsoluteSize.Y
newX = math.clamp(newX, -fw + 40, screenW - 40)
newY = math.clamp(newY, -fh + 24, screenH - 24)
MainFrame.Position = UDim2.new(0, newX, 0, newY)
end
TitleBar.InputBegan:Connect(beginDrag)
TitleBar.InputChanged:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then
updateDrag(input)
end
end)
MiniControls.InputBegan:Connect(beginDrag)
MiniControls.InputChanged:Connect(function(input)
if input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch then
updateDrag(input)
end
end)
UserInputService.InputChanged:Connect(function(input)
if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then
updateDrag(input)
end
end)
UserInputService.InputBegan:Connect(function(input, gameProcessed)
pcall(function()
if gameProcessed then return end
if (input.KeyCode == Enum.KeyCode.LeftControl or input.KeyCode == Enum.KeyCode.RightControl) and isSpying and isManualCameraActive then
ctrlLockActive = not ctrlLockActive
if ctrlLockActive then
isMouseDown = true
pcall(function() UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter end)
pcall(function() UserInputService.MouseIconEnabled = false end)
else
isMouseDown = false
pcall(function() UserInputService.MouseBehavior = Enum.MouseBehavior.Default end)
pcall(function() UserInputService.MouseIconEnabled = true end)
end
return
end
if not isSpying or not isManualCameraActive then return end
local isControlInput = input.UserInputType == Enum.UserInputType.MouseButton2 or input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.Gamepad1
if isControlInput and not isMouseDown then
if input.UserInputType == Enum.UserInputType.MouseButton2 then
isMouseDown = true
UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
elseif input.UserInputType == Enum.UserInputType.Touch then
isMouseDown = true
touchId = input.UserInputType
elseif input.UserInputType == Enum.UserInputType.Gamepad1 and input.KeyCode == Enum.KeyCode.Thumbstick2 then
isMouseDown = true
end
end
end)
end)
UserInputService.InputEnded:Connect(function(input, gameProcessed)
pcall(function()
if not isSpying or not isManualCameraActive then return end
local isControlInput = input.UserInputType == Enum.UserInputType.MouseButton2 or input.UserInputType == Enum.UserInputType.Touch or input.UserInputType == Enum.UserInputType.Gamepad1
if isControlInput and isMouseDown and not ctrlLockActive then
if input.UserInputType == Enum.UserInputType.MouseButton2 then
isMouseDown = false
UserInputService.MouseBehavior = Enum.MouseBehavior.Default
elseif input.UserInputType == Enum.UserInputType.Touch then
isMouseDown = false
touchId = nil
elseif input.UserInputType == Enum.UserInputType.Gamepad1 and input.KeyCode == Enum.KeyCode.Thumbstick2 then
isMouseDown = false
end
end
end)
end)
UserInputService.InputChanged:Connect(function(input, gameProcessed)
pcall(function()
if not isSpying or not isManualCameraActive then return end
if isMouseDown then
if input.UserInputType == Enum.UserInputType.MouseMovement then
local delta = input.Delta * CAMERA_ROTATION_SENSITIVITY
cameraAngle = cameraAngle + Vector2.new(-delta.X, -delta.Y)
elseif input.UserInputType == Enum.UserInputType.Touch and touchId then
local delta = input.Delta * CAMERA_ROTATION_SENSITIVITY * (isMobile and 1 or 2)
cameraAngle = cameraAngle + Vector2.new(-delta.X, -delta.Y)
elseif input.UserInputType == Enum.UserInputType.Gamepad1 and input.KeyCode == Enum.KeyCode.Thumbstick2 then
local delta = Vector2.new(-input.Position.X, input.Position.Y) * CAMERA_ROTATION_SENSITIVITY * 10
cameraAngle = cameraAngle + delta
end
cameraAngle = Vector2.new(cameraAngle.X, math.clamp(cameraAngle.Y, -math.pi/2 + 0.01, math.pi/2 - 0.01))
end
if input.UserInputType == Enum.UserInputType.MouseWheel then
local mousePos = UserInputService:GetMouseLocation()
local absPos = MainFrame.AbsolutePosition
local absSize = MainFrame.AbsoluteSize
local overUI = mousePos.X >= absPos.X and mousePos.X <= absPos.X + absSize.X
and mousePos.Y >= absPos.Y and mousePos.Y <= absPos.Y + absSize.Y
if not overUI then
zoomDelta = zoomDelta + input.Position.Z * CAMERA_ZOOM_SPEED
end
end
if input.UserInputType == Enum.UserInputType.Gamepad1 then
if input.KeyCode == Enum.KeyCode.ButtonL1 or input.KeyCode == Enum.KeyCode.ButtonL2 then
if input.UserInputState == Enum.UserInputState.Begin then zoomDelta = zoomDelta - CAMERA_ZOOM_SPEED * 2 end
elseif input.KeyCode == Enum.KeyCode.ButtonR1 or input.KeyCode == Enum.KeyCode.ButtonR2 then
if input.UserInputState == Enum.UserInputState.Begin then zoomDelta = zoomDelta + CAMERA_ZOOM_SPEED * 2 end
end
end
end)
end)
UserInputService.TouchPinch:Connect(function(touchPositions, scale, velocity, state)
pcall(function()
if not isSpying or not isManualCameraActive then return end
if state == Enum.UserInputState.Begin then lastTouchScale = scale
elseif state == Enum.UserInputState.Change then
if lastTouchScale then
local difference = scale - lastTouchScale
zoomDelta = zoomDelta + difference * PINCH_ZOOM_SENSITIVITY
lastTouchScale = scale
end
elseif state == Enum.UserInputState.End then lastTouchScale = nil end
end)
end)
UserInputService.InputBegan:Connect(function(input, gameProcessed)
if gameProcessed then return end
if input.KeyCode == Enum.KeyCode.Escape then
if isMinimized then maximizeUI() else minimizeUI() end
end
end)
MainFrame.Position = UDim2.new(0, SIZES.MainX, 0, -500)
tweenGui(MainFrame, {Position = UDim2.new(0, SIZES.MainX, 0, SIZES.MainY)}, TWEEN_INFO_SLOW)
tweenGui(MainFrame, {Size = UDim2.new(0, SIZES.MainW, 0, SIZES.MainH)}, TWEEN_INFO_SLOW)No comments yet for this script.



