telekinesis pc y moviles V4
Key RequiredUniversal

telekinesis pc y moviles V4

Universal ScriptCleoooCleooo 16.4K views 10.8K executesUpdated Aug 23, 2025
5.0/ 5(3 votes)
30100% Positive
Lua Script•3346 lines (57956 chars)
--[[
	TELEKINESIS V4.4
	PC + MOBILE UNIFICADO

	CAMBIOS DE ESTA VERSIÓN
	- Ctrl derecho = mostrar / ocultar interfaz en PC
	- Supr deja de controlar la interfaz
	- Ocultar interfaz NO desactiva el sistema
	- Móvil puede ocultar / mostrar correctamente
	- Botón de mostrar móvil arriba a la izquierda
	- Highlights se restauran al mostrar
	- Imán:
		- controles arriba
		- Desactivar abajo izquierda
		- Anclar todo abajo derecha
	- Rotación:
		- flechas abajo izquierda
		- no ocupan el centro
	- Cursor visual conservado
	- Motor PC / móvil unificado
]]

--------------------------------------------------
-- SERVICIOS
--------------------------------------------------

local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local StarterGui = game:GetService("StarterGui")
local Debris = game:GetService("Debris")
local CoreGui = game:GetService("CoreGui")

local player = Players.LocalPlayer
local mouse = player:GetMouse()
local camera = workspace.CurrentCamera

local isMobile = UserInputService.TouchEnabled

--------------------------------------------------
-- CONFIG
--------------------------------------------------

local CONFIG = {
	-- Lanzamiento
	throwForce = 1000,
	throwForceMin = 100,
	throwForceMax = 4000,
	throwForceStepPC = 100,
	throwForceStepMobile = 200,

	-- Imán
	magnetRadius = 30,
	magnetRadiusMin = 5,
	magnetRadiusMax = 200,

	magnetForce = 200,
	magnetForceMin = 50,
	magnetForceMax = 2000,

	magnetForceStep = 25,
	magnetRadiusStep = 2,

	-- Agarre
	holdDistanceDefault = 10,
	holdDistanceMin = 2,
	holdDistanceMax = 1000,

	distanceSpeed = 0.8,
	distanceKeySpeed = 0.4,

	-- Rotación
	rotationSpeed = 2,

	-- Física
	holdVelocityP = 12500,
	holdGyroP = 3000,

	-- Bloqueo de posición
	rotationPositionP = 20000,
	rotationPositionD = 1250,

	-- Cursor móvil
	pointerDragThreshold = 10,
}

--------------------------------------------------
-- ESTADO
--------------------------------------------------

local STATE = {
	active = true,

	-- IMPORTANTE:
	-- guiVisible controla solo la interfaz.
	guiVisible = true,

	holding = false,
	target = nil,

	holdDistance = CONFIG.holdDistanceDefault,
	anchoredWhileHolding = false,

	magnetActive = false,
	magnetObjects = {},

	rotationMode = false,

	rotationKeys = {
		up = false,
		down = false,
		left = false,
		right = false,
	},

	holdQ = false,
	holdE = false,

	holdClose = false,
	holdFar = false,

	rotationPosition = nil,

	-- Personaje
	characterVisibilityBackup = {},
	characterFrozen = false,
	characterBackup = nil,

	-- Cursor móvil
	pointerTouch = nil,
	pointerTouchStart = nil,
	pointerDragStarted = false,

	-- Modo móvil
	mobileMode = "normal",

	-- Notificaciones
	lastNotificationTime = 0,
	notificationCooldown = 0.25,
}

--------------------------------------------------
-- PERSONAJE
--------------------------------------------------

local function getCharacter()
	return player.Character
end

local function getHumanoid()
	local character = getCharacter()

	return character
		and character:FindFirstChildOfClass("Humanoid")
end

local function getRootPart()
	local character = getCharacter()

	return character
		and character:FindFirstChild("HumanoidRootPart")
end

--------------------------------------------------
-- FÍSICA
--------------------------------------------------

local bodyVelocity = Instance.new("BodyVelocity")
bodyVelocity.Name = "TelekinesisVelocity"
bodyVelocity.MaxForce = Vector3.new(1, 1, 1) * 1e5
bodyVelocity.P = CONFIG.holdVelocityP
bodyVelocity.Velocity = Vector3.zero

local bodyGyro = Instance.new("BodyGyro")
bodyGyro.Name = "TelekinesisGyro"
bodyGyro.MaxTorque = Vector3.new(1, 1, 1) * 1e6
bodyGyro.P = CONFIG.holdGyroP

local rotationPositionLock = Instance.new("BodyPosition")
rotationPositionLock.Name = "TelekinesisRotationPosition"
rotationPositionLock.MaxForce = Vector3.new(1, 1, 1) * 1e7
rotationPositionLock.P = CONFIG.rotationPositionP
rotationPositionLock.D = CONFIG.rotationPositionD
rotationPositionLock.Position = Vector3.zero

--------------------------------------------------
-- HIGHLIGHTS
--------------------------------------------------

local highlightHolding = Instance.new("Highlight")
highlightHolding.Name = "TelekinesisHolding"
highlightHolding.FillColor = Color3.fromRGB(0, 255, 0)
highlightHolding.OutlineColor = Color3.fromRGB(0, 255, 0)
highlightHolding.FillTransparency = 0.5
highlightHolding.OutlineTransparency = 0
highlightHolding.DepthMode = Enum.HighlightDepthMode.Occluded
highlightHolding.Enabled = true
highlightHolding.Parent = CoreGui

local highlightLooking = Instance.new("Highlight")
highlightLooking.Name = "TelekinesisLooking"
highlightLooking.FillColor = Color3.fromRGB(0, 170, 255)
highlightLooking.OutlineColor = Color3.fromRGB(0, 170, 255)
highlightLooking.FillTransparency = 0.6
highlightLooking.OutlineTransparency = 0
highlightLooking.DepthMode = Enum.HighlightDepthMode.Occluded
highlightLooking.Enabled = true
highlightLooking.Parent = CoreGui

--------------------------------------------------
-- VISUAL IMÁN
--------------------------------------------------

local usingDrawing =
	Drawing
	and Drawing.new
	and typeof(Drawing.new) == "function"

local magnetCircle
local circleGui
local circleImage

local surfacePosition = Vector3.zero
local actualMagnetRadius = CONFIG.magnetRadius

if usingDrawing then
	magnetCircle = Drawing.new("Circle")
	magnetCircle.Visible = false
	magnetCircle.Transparency = 1
	magnetCircle.Color = Color3.fromRGB(0, 170, 255)
	magnetCircle.Thickness = 2
	magnetCircle.Filled = false
else
	circleGui = Instance.new("BillboardGui")
	circleGui.Name = "TelekinesisMagnetCircle"
	circleGui.Size = UDim2.fromOffset(100, 100)
	circleGui.AlwaysOnTop = true
	circleGui.Enabled = false
	circleGui.Parent = CoreGui

	circleImage = Instance.new("ImageLabel")
	circleImage.BackgroundTransparency = 1
	circleImage.Image = "rbxassetid://13523341990"
	circleImage.ImageColor3 = Color3.fromRGB(0, 170, 255)
	circleImage.AnchorPoint = Vector2.new(0.5, 0.5)
	circleImage.Position = UDim2.fromScale(0.5, 0.5)
	circleImage.Size = UDim2.fromScale(1, 1)
	circleImage.Parent = circleGui
end

--------------------------------------------------
-- UI
--------------------------------------------------

local UI = {}

--------------------------------------------------
-- UI HELPERS
--------------------------------------------------

local function makeCorner(object, radius)
	local corner = Instance.new("UICorner")
	corner.CornerRadius = UDim.new(0, radius or 12)
	corner.Parent = object
	return corner
end

local function makeStroke(object, transparency)
	local stroke = Instance.new("UIStroke")
	stroke.Color = Color3.fromRGB(255, 255, 255)
	stroke.Transparency = transparency or 0.75
	stroke.Thickness = 1
	stroke.Parent = object
	return stroke
end

local function styleButton(button)
	button.AutoButtonColor = false
	makeCorner(button, 14)
	makeStroke(button, 0.78)
	return button
end

--------------------------------------------------
-- GUI PC
--------------------------------------------------

local function createMainGUI()
	local gui = Instance.new("ScreenGui")
	gui.Name = "TelekinesisUI"
	gui.ResetOnSpawn = false
	gui.IgnoreGuiInset = true
	gui.Parent = CoreGui

	UI.mainGui = gui

	local forceLabel = Instance.new("TextLabel")
	forceLabel.Name = "ForceLabel"
	forceLabel.BackgroundTransparency = 0.3
	forceLabel.BackgroundColor3 = Color3.fromRGB(25, 25, 25)
	forceLabel.Size = UDim2.fromOffset(220, 60)
	forceLabel.Position = UDim2.new(1, -230, 1, -70)
	forceLabel.TextScaled = true
	forceLabel.Font = Enum.Font.GothamBold
	forceLabel.TextColor3 = Color3.new(1, 1, 1)
	forceLabel.TextStrokeTransparency = 0.5
	forceLabel.Visible = not isMobile

	makeCorner(forceLabel, 14)
	makeStroke(forceLabel, 0.8)

	forceLabel.Parent = gui
	UI.forceLabel = forceLabel

	local rotationLabel = Instance.new("TextLabel")
	rotationLabel.Name = "RotationLabel"
	rotationLabel.BackgroundTransparency = 0.3
	rotationLabel.BackgroundColor3 = Color3.fromRGB(25, 25, 25)
	rotationLabel.Size = UDim2.fromOffset(260, 70)
	rotationLabel.Position = UDim2.new(1, -270, 1, -80)
	rotationLabel.TextScaled = true
	rotationLabel.Font = Enum.Font.GothamBold
	rotationLabel.TextColor3 = Color3.new(1, 1, 1)
	rotationLabel.Text = "🔄 ROTACIÓN\nWASD / ↑↓←→"
	rotationLabel.Visible = false

	makeCorner(rotationLabel, 14)
	makeStroke(rotationLabel, 0.8)

	rotationLabel.Parent = gui
	UI.rotationLabel = rotationLabel
end

--------------------------------------------------
-- UI MÓVIL
--------------------------------------------------

local function createMobileUI()
	local gui = Instance.new("ScreenGui")
	gui.Name = "TelekinesisMobile"
	gui.ResetOnSpawn = false
	gui.IgnoreGuiInset = true
	gui.Parent = CoreGui

	UI.mobileGui = gui

	--------------------------------------------------
	-- CURSOR VISUAL
	--------------------------------------------------

	local fakePointer = Instance.new("Frame")

	fakePointer.Name = "FakePointer"
	fakePointer.Size = UDim2.fromOffset(35, 35)
	fakePointer.Position = UDim2.new(0.5, 0, 0.4, 0)
	fakePointer.AnchorPoint = Vector2.new(0.5, 0.5)

	fakePointer.BackgroundColor3 =
		Color3.fromRGB(0, 170, 255)

	fakePointer.BackgroundTransparency = 0.4
	fakePointer.Active = true
	fakePointer.ZIndex = 50
	fakePointer.Parent = gui

	makeCorner(fakePointer, 35)
	makeStroke(fakePointer, 0.25)

	UI.fakePointer = fakePointer

	--------------------------------------------------
	-- CONTENEDOR
	--------------------------------------------------

	local container = Instance.new("Frame")
	container.Name = "Controls"
	container.Size = UDim2.fromScale(1, 1)
	container.BackgroundTransparency = 1
	container.Parent = gui

	UI.mobileContainer = container

	--------------------------------------------------
	-- CABECERA
	--------------------------------------------------

	local header = Instance.new("Frame")
	header.Name = "Header"
	header.Size = UDim2.new(0, 230, 0, 55)
	header.Position = UDim2.new(0.5, -115, 0, 12)

	header.BackgroundColor3 =
		Color3.fromRGB(25, 25, 25)

	header.BackgroundTransparency = 0.18
	header.ZIndex = 10

	header.Parent = container

	makeCorner(header, 16)
	makeStroke(header, 0.7)

	UI.header = header

	--------------------------------------------------
	-- MODO
	--------------------------------------------------

	local modeLabel = Instance.new("TextLabel")

	modeLabel.Name = "ModeLabel"
	modeLabel.BackgroundTransparency = 1
	modeLabel.Size = UDim2.new(1, -55, 1, 0)
	modeLabel.Position = UDim2.fromOffset(8, 0)

	modeLabel.Font = Enum.Font.GothamBold
	modeLabel.TextScaled = true
	modeLabel.TextColor3 = Color3.new(1, 1, 1)
	modeLabel.Text = "🎯 NORMAL"
	modeLabel.ZIndex = 11

	modeLabel.Parent = header

	UI.modeLabel = modeLabel

	--------------------------------------------------
	-- OJO
	--------------------------------------------------

	local hideButton = Instance.new("TextButton")

	hideButton.Name = "Hide"
	hideButton.Size = UDim2.fromOffset(45, 45)
	hideButton.Position = UDim2.new(1, -50, 0, 5)

	hideButton.BackgroundColor3 =
		Color3.fromRGB(70, 70, 70)

	hideButton.BackgroundTransparency = 0.15
	hideButton.Text = "👁️"
	hideButton.TextScaled = true
	hideButton.TextColor3 = Color3.new(1, 1, 1)
	hideButton.ZIndex = 12

	styleButton(hideButton)
	hideButton.Parent = header

	UI.hideButton = hideButton

	--------------------------------------------------
	-- PANEL
	--------------------------------------------------

	local panel = Instance.new("Frame")

	panel.Name = "ModePanel"
	panel.Size = UDim2.new(1, 0, 0, 300)
	panel.Position = UDim2.new(0, 0, 1, -305)
	panel.BackgroundTransparency = 1
	panel.ZIndex = 5

	panel.Parent = container

	UI.panel = panel

	--------------------------------------------------
	-- BOTÓN CENTRAL
	--------------------------------------------------

	local actionButton = Instance.new("TextButton")

	actionButton.Name = "Action"
	actionButton.Size = UDim2.fromOffset(190, 68)
	actionButton.Position =
		UDim2.new(0.5, -95, 1, -80)

	actionButton.BackgroundColor3 =
		Color3.fromRGB(0, 190, 100)

	actionButton.BackgroundTransparency = 0.05
	actionButton.Text = "✋ AGARRAR"
	actionButton.TextScaled = true
	actionButton.Font = Enum.Font.GothamBold
	actionButton.TextColor3 = Color3.new(1, 1, 1)
	actionButton.ZIndex = 10

	styleButton(actionButton)
	actionButton.Parent = panel

	UI.actionButton = actionButton

	--------------------------------------------------
	-- CREAR BOTÓN
	--------------------------------------------------

	local function makeSmallButton(
		name,
		position,
		size,
		text,
		color
	)

		local button = Instance.new("TextButton")

		button.Name = name
		button.Size = size
		button.Position = position

		button.BackgroundColor3 = color
		button.BackgroundTransparency = 0.15

		button.Text = text
		button.TextScaled = true
		button.Font = Enum.Font.GothamBold
		button.TextColor3 = Color3.new(1, 1, 1)
		button.ZIndex = 10

		styleButton(button)

		button.Parent = panel

		return button
	end

	--------------------------------------------------
	-- NORMAL
	--------------------------------------------------

	UI.buttons = {}

	UI.buttons.distanceDown =
		makeSmallButton(
			"DistanceDown",
			UDim2.new(0, 12, 1, -155),
			UDim2.fromOffset(72, 55),
			"📏\n−",
			Color3.fromRGB(60, 135, 200)
		)

	UI.buttons.distanceUp =
		makeSmallButton(
			"DistanceUp",
			UDim2.new(0, 92, 1, -155),
			UDim2.fromOffset(72, 55),
			"📏\n+",
			Color3.fromRGB(60, 135, 200)
		)

	UI.buttons.throw =
		makeSmallButton(
			"Throw",
			UDim2.new(1, -92, 1, -155),
			UDim2.fromOffset(80, 58),
			"🔥\nLANZAR",
			Color3.fromRGB(210, 70, 65)
		)

	UI.buttons.anchor =
		makeSmallButton(
			"Anchor",
			UDim2.new(1, -178, 1, -155),
			UDim2.fromOffset(78, 58),
			"📌\nANCLAR",
			Color3.fromRGB(205, 150, 45)
		)

	UI.buttons.forceDown =
		makeSmallButton(
			"ForceDown",
			UDim2.new(0, 12, 1, -220),
			UDim2.fromOffset(72, 50),
			"🎯 −",
			Color3.fromRGB(145, 70, 70)
		)

	UI.buttons.forceUp =
		makeSmallButton(
			"ForceUp",
			UDim2.new(0, 92, 1, -220),
			UDim2.fromOffset(72, 50),
			"🎯 +",
			Color3.fromRGB(65, 150, 85)
		)

	UI.buttons.magnet =
		makeSmallButton(
			"Magnet",
			UDim2.new(1, -92, 1, -220),
			UDim2.fromOffset(80, 50),
			"🧲 IMÁN",
			Color3.fromRGB(55, 130, 205)
		)

	UI.buttons.rotation =
		makeSmallButton(
			"Rotation",
			UDim2.new(1, -178, 1, -220),
			UDim2.fromOffset(78, 50),
			"🔄 ROTAR",
			Color3.fromRGB(120, 85, 190)
		)

	--------------------------------------------------
	-- VALOR
	--------------------------------------------------

	local valueLabel = Instance.new("TextLabel")

	valueLabel.Name = "Value"
	valueLabel.Size = UDim2.fromOffset(180, 42)
	valueLabel.Position =
		UDim2.new(0.5, -90, 1, -140)

	valueLabel.BackgroundColor3 =
		Color3.fromRGB(25, 25, 25)

	valueLabel.BackgroundTransparency = 0.18
	valueLabel.TextScaled = true
	valueLabel.Font = Enum.Font.GothamBold
	valueLabel.TextColor3 = Color3.new(1, 1, 1)
	valueLabel.ZIndex = 9

	makeCorner(valueLabel, 12)
	makeStroke(valueLabel, 0.8)

	valueLabel.Parent = panel
	UI.valueLabel = valueLabel

	--------------------------------------------------
	-- IMÁN
	--------------------------------------------------

	UI.magnetButtons = {}

	UI.magnetButtons.forceDown =
		makeSmallButton(
			"MagnetForceDown",
			UDim2.new(0, 12, 1, -220),
			UDim2.fromOffset(90, 55),
			"⚡ −",
			Color3.fromRGB(145, 70, 70)
		)

	UI.magnetButtons.forceUp =
		makeSmallButton(
			"MagnetForceUp",
			UDim2.new(0, 108, 1, -220),
			UDim2.fromOffset(90, 55),
			"⚡ +",
			Color3.fromRGB(65, 150, 85)
		)

	UI.magnetButtons.radiusDown =
		makeSmallButton(
			"RadiusDown",
			UDim2.new(1, -204, 1, -220),
			UDim2.fromOffset(90, 55),
			"📏 −",
			Color3.fromRGB(60, 135, 200)
		)

	UI.magnetButtons.radiusUp =
		makeSmallButton(
			"RadiusUp",
			UDim2.new(1, -108, 1, -220),
			UDim2.fromOffset(90, 55),
			"📏 +",
			Color3.fromRGB(60, 135, 200)
		)

	--------------------------------------------------
	-- CAMBIO PEDIDO:
	-- DESACTIVAR ABAJO IZQUIERDA
	--------------------------------------------------

	UI.magnetButtons.exit =
		makeSmallButton(
			"MagnetExit",
			UDim2.new(0, 20, 1, -105),
			UDim2.fromOffset(175, 58),
			"🧲 DESACTIVAR",
			Color3.fromRGB(180, 65, 65)
		)

	--------------------------------------------------
	-- CAMBIO PEDIDO:
	-- ANCLAR TODO ABAJO DERECHA
	--------------------------------------------------

	UI.magnetButtons.massAnchor =
		makeSmallButton(
			"MassAnchor",
			UDim2.new(1, -195, 1, -105),
			UDim2.fromOffset(175, 58),
			"📌 ANCLAR TODO",
			Color3.fromRGB(200, 145, 35)
		)

	--------------------------------------------------
	-- ROTACIÓN
	--------------------------------------------------

	UI.rotationButtons = {}

	--------------------------------------------------
	-- CAMBIO PEDIDO:
	-- TODO EL PAD ABAJO IZQUIERDA
	--------------------------------------------------

	UI.rotationButtons.up =
		makeSmallButton(
			"RotateUp",
			UDim2.new(0, 102, 1, -165),
			UDim2.fromOffset(62, 55),
			"▲",
			Color3.fromRGB(100, 80, 190)
		)

	UI.rotationButtons.left =
		makeSmallButton(
			"RotateLeft",
			UDim2.new(0, 34, 1, -105),
			UDim2.fromOffset(62, 55),
			"◀",
			Color3.fromRGB(100, 80, 190)
		)

	UI.rotationButtons.right =
		makeSmallButton(
			"RotateRight",
			UDim2.new(0, 170, 1, -105),
			UDim2.fromOffset(62, 55),
			"▶",
			Color3.fromRGB(100, 80, 190)
		)

	UI.rotationButtons.down =
		makeSmallButton(
			"RotateDown",
			UDim2.new(0, 102, 1, -45),
			UDim2.fromOffset(62, 55),
			"▼",
			Color3.fromRGB(100, 80, 190)
		)

	UI.rotationButtons.exit =
		makeSmallButton(
			"RotateExit",
			UDim2.new(1, -175, 1, -100),
			UDim2.fromOffset(155, 55),
			"✅ TERMINAR",
			Color3.fromRGB(65, 145, 90)
		)

	--------------------------------------------------
	-- OCULTAR MODOS AL INICIO
	--------------------------------------------------

	for _, button in pairs(UI.magnetButtons) do
		button.Visible = false
	end

	for _, button in pairs(UI.rotationButtons) do
		button.Visible = false
	end
end

createMainGUI()

if isMobile then
	createMobileUI()
end

--------------------------------------------------
-- NOTIFICACIONES
--------------------------------------------------

local function notify(title, text, duration)

	if not STATE.active then
		return
	end

	local now = os.clock()

	if now - STATE.lastNotificationTime
		< STATE.notificationCooldown then

		return
	end

	STATE.lastNotificationTime = now

	pcall(function()
		StarterGui:SetCore(
			"SendNotification",
			{
				Title = title,
				Text = text,
				Duration = duration or 1.2,
			}
		)
	end)
end

--------------------------------------------------
-- UTILIDADES
--------------------------------------------------

local function isPlayerPart(part)

	if not part then
		return false
	end

	for _, otherPlayer in ipairs(
		Players:GetPlayers()
	) do

		local character =
			otherPlayer.Character

		if character
			and part:IsDescendantOf(character) then

			return true
		end
	end

	return false
end

local function hasUnanchoredWeld(part)

	for _, connectedPart in ipairs(
		part:GetConnectedParts()
	) do

		if connectedPart ~= part
			and not connectedPart.Anchored then

			return true
		end
	end

	return false
end

--------------------------------------------------
-- VISIBILIDAD DEL PERSONAJE
--------------------------------------------------

local function hideCharacter()

	local character =
		player.Character

	if not character then
		return
	end

	if next(
		STATE.characterVisibilityBackup
	) ~= nil then

		return
	end

	for _, object in ipairs(
		character:GetDescendants()
	) do

		if object:IsA("BasePart") then

			STATE.characterVisibilityBackup[
				object
			] =
				object.LocalTransparencyModifier

			object.LocalTransparencyModifier = 1

		elseif object:IsA("Decal")
			or object:IsA("Texture") then

			STATE.characterVisibilityBackup[
				object
			] =
				object.Transparency

			object.Transparency = 1
		end
	end
end

local function showCharacter()

	for object, transparency in pairs(
		STATE.characterVisibilityBackup
	) do

		if object
			and object:IsDescendantOf(workspace) then

			if object:IsA("BasePart") then

				object.LocalTransparencyModifier =
					transparency

			elseif object:IsA("Decal")
				or object:IsA("Texture") then

				object.Transparency =
					transparency
			end
		end
	end

	STATE.characterVisibilityBackup = {}
end

--------------------------------------------------
-- CONGELAR PERSONAJE
--------------------------------------------------

local function freezeCharacter()

	if STATE.characterFrozen then
		return
	end

	local humanoid = getHumanoid()
	local root = getRootPart()

	if not humanoid or not root then
		return
	end

	STATE.characterBackup = {
		WalkSpeed = humanoid.WalkSpeed,
		JumpPower = humanoid.JumpPower,
		JumpHeight = humanoid.JumpHeight,
		AutoRotate = humanoid.AutoRotate,
		RootAnchored = root.Anchored,
	}

	STATE.characterFrozen = true

	humanoid.WalkSpeed = 0
	humanoid.JumpPower = 0
	humanoid.JumpHeight = 0
	humanoid.AutoRotate = false

	root.AssemblyLinearVelocity =
		Vector3.zero

	root.AssemblyAngularVelocity =
		Vector3.zero

	root.Anchored = true
end

local function unfreezeCharacter()

	if not STATE.characterFrozen then
		return
	end

	local humanoid = getHumanoid()
	local root = getRootPart()
	local backup = STATE.characterBackup

	STATE.characterFrozen = false
	STATE.characterBackup = nil

	if humanoid and backup then

		humanoid.WalkSpeed =
			backup.WalkSpeed

		humanoid.JumpPower =
			backup.JumpPower

		humanoid.JumpHeight =
			backup.JumpHeight

		humanoid.AutoRotate =
			backup.AutoRotate
	end

	if root and backup then
		root.Anchored =
			backup.RootAnchored
	end
end

--------------------------------------------------
-- FÍSICA
--------------------------------------------------

local function disconnectPhysics()

	bodyVelocity.Parent = nil
	bodyGyro.Parent = nil
end

local function connectPhysics(target)

	if not target then
		return
	end

	bodyVelocity.Parent = target
	bodyGyro.Parent = target
end

--------------------------------------------------
-- RAY
--------------------------------------------------

local function getAimRay()

	if isMobile and UI.fakePointer then

		local position =
			UI.fakePointer.AbsolutePosition

		local size =
			UI.fakePointer.AbsoluteSize

		local x =
			position.X
			+ size.X * 0.5

		local y =
			position.Y
			+ size.Y * 0.5

		local ray =
			camera:ScreenPointToRay(x, y)

		return Ray.new(
			ray.Origin,
			ray.Direction * 1000
		)
	end

	local ray =
		camera:ScreenPointToRay(
			mouse.X,
			mouse.Y
		)

	return Ray.new(
		ray.Origin,
		ray.Direction * 1000
	)
end

local function raycastNormal()

	local ray =
		getAimRay()

	return workspace:FindPartOnRayWithIgnoreList(
		ray,
		{
			getCharacter()
		}
	)
end

local function raycastForMagnet()

	local ray =
		getAimRay()

	local ignoreList = {
		getCharacter()
	}

	for _ = 1, 100 do

		local hit, position, normal =
			workspace:FindPartOnRayWithIgnoreList(
				ray,
				ignoreList
			)

		if not hit then
			return nil, nil, nil
		end

		if isPlayerPart(hit) then

			table.insert(
				ignoreList,
				hit
			)

		elseif hit:IsA("BasePart")
			and not hit.Anchored then

			table.insert(
				ignoreList,
				hit
			)

		else

			return hit, position, normal
		end
	end

	return nil, nil, nil
end

--------------------------------------------------
-- HIGHLIGHTS
--------------------------------------------------

local function refreshHighlights()

	if not STATE.guiVisible then

		highlightHolding.Enabled = false
		highlightLooking.Enabled = false

		return
	end

	highlightHolding.Enabled = true
	highlightLooking.Enabled = true

	if STATE.holding and STATE.target then

		highlightHolding.Adornee =
			STATE.target

		highlightLooking.Adornee = nil

	elseif not STATE.holding then

		highlightHolding.Adornee = nil

		local hit = raycastNormal()

		if hit
			and hit:IsA("BasePart")
			and not hit.Anchored
			and not isPlayerPart(hit) then

			highlightLooking.Adornee =
				hit

		else

			highlightLooking.Adornee = nil
		end

	else

		highlightHolding.Adornee = nil
		highlightLooking.Adornee = nil
	end
end

--------------------------------------------------
-- MOSTRAR / OCULTAR UI
--------------------------------------------------

local function setInterfaceVisible(visible)

	STATE.guiVisible = visible

	if not isMobile then

		if UI.forceLabel then
			UI.forceLabel.Visible =
				visible
		end

		if UI.rotationLabel then
			UI.rotationLabel.Visible =
				visible
				and STATE.rotationMode
		end

	else

		if UI.mobileContainer then
			UI.mobileContainer.Visible =
				visible
		end

		if UI.fakePointer then
			UI.fakePointer.Visible =
				visible
		end
	end

	--------------------------------------------------
	-- EL CÍRCULO DEL IMÁN
	-- también depende de la visibilidad.
	--------------------------------------------------

	if not visible then

		if usingDrawing
			and magnetCircle then

			magnetCircle.Visible = false

		elseif circleGui then

			circleGui.Enabled = false
		end
	end

	refreshHighlights()
end

local function toggleInterface()

	setInterfaceVisible(
		not STATE.guiVisible
	)
end

--------------------------------------------------
-- MÓVIL: UI NORMAL
--------------------------------------------------

local function updateMobileNormalUI()

	if not isMobile then
		return
	end

	STATE.mobileMode =
		"normal"

	UI.modeLabel.Text =
		"🎯 NORMAL"

	--------------------------------------------------
	-- NORMAL
	--------------------------------------------------

	UI.buttons.distanceDown.Visible = true
	UI.buttons.distanceUp.Visible = true
	UI.buttons.throw.Visible = true
	UI.buttons.anchor.Visible = true
	UI.buttons.forceDown.Visible = true
	UI.buttons.forceUp.Visible = true
	UI.buttons.magnet.Visible = true
	UI.buttons.rotation.Visible = true
	UI.actionButton.Visible = true
	UI.valueLabel.Visible = true

	--------------------------------------------------
	-- OCULTAR OTROS MODOS
	--------------------------------------------------

	for _, button in pairs(
		UI.magnetButtons
	) do
		button.Visible = false
	end

	for _, button in pairs(
		UI.rotationButtons
	) do
		button.Visible = false
	end

	--------------------------------------------------
	-- VALOR
	--------------------------------------------------

	UI.valueLabel.Text =
		"🎯 "
		.. math.floor(
			CONFIG.throwForce
		)

	--------------------------------------------------
	-- AGARRAR / SOLTAR
	--------------------------------------------------

	if STATE.holding then

		UI.actionButton.Text =
			"❌ SOLTAR"

		UI.actionButton.BackgroundColor3 =
			Color3.fromRGB(
				190,
				55,
				55
			)

	else

		UI.actionButton.Text =
			"✋ AGARRAR"

		UI.actionButton.BackgroundColor3 =
			Color3.fromRGB(
				0,
				190,
				100
			)
	end

	refreshHighlights()
end

--------------------------------------------------
-- MÓVIL: UI IMÁN
--------------------------------------------------

local function updateMobileMagnetUI()

	if not isMobile then
		return
	end

	STATE.mobileMode =
		"magnet"

	UI.modeLabel.Text =
		"🧲 MODO IMÁN"

	--------------------------------------------------
	-- OCULTAR NORMAL
	--------------------------------------------------

	UI.buttons.distanceDown.Visible = false
	UI.buttons.distanceUp.Visible = false
	UI.buttons.throw.Visible = false
	UI.buttons.anchor.Visible = false
	UI.buttons.forceDown.Visible = false
	UI.buttons.forceUp.Visible = false
	UI.buttons.magnet.Visible = false
	UI.buttons.rotation.Visible = false
	UI.actionButton.Visible = false

	--------------------------------------------------
	-- MOSTRAR IMÁN
	--------------------------------------------------

	for _, button in pairs(
		UI.magnetButtons
	) do
		button.Visible = true
	end

	for _, button in pairs(
		UI.rotationButtons
	) do
		button.Visible = false
	end

	--------------------------------------------------
	-- VALOR
	--------------------------------------------------

	UI.valueLabel.Visible = true

	UI.valueLabel.Text =
		"⚡ "
		.. math.floor(
			CONFIG.magnetForce
		)
		.. "   •   📏 "
		.. math.floor(
			CONFIG.magnetRadius
		)
end

--------------------------------------------------
-- MÓVIL: UI ROTACIÓN
--------------------------------------------------

local function updateMobileRotationUI()

	if not isMobile then
		return
	end

	STATE.mobileMode =
		"rotation"

	UI.modeLabel.Text =
		"🔄 MODO ROTACIÓN"

	--------------------------------------------------
	-- OCULTAR NORMAL
	--------------------------------------------------

	UI.buttons.distanceDown.Visible = false
	UI.buttons.distanceUp.Visible = false
	UI.buttons.throw.Visible = false
	UI.buttons.anchor.Visible = false
	UI.buttons.forceDown.Visible = false
	UI.buttons.forceUp.Visible = false
	UI.buttons.magnet.Visible = false
	UI.buttons.rotation.Visible = false
	UI.actionButton.Visible = false

	--------------------------------------------------
	-- ROTACIÓN
	--------------------------------------------------

	for _, button in pairs(
		UI.magnetButtons
	) do
		button.Visible = false
	end

	for _, button in pairs(
		UI.rotationButtons
	) do
		button.Visible = true
	end

	--------------------------------------------------
	-- VALOR
	--------------------------------------------------

	UI.valueLabel.Visible = true
	UI.valueLabel.Text = "↑ ↓ ← →   •   MANTÉN"
end

--------------------------------------------------
-- AGARRAR
--------------------------------------------------

local function grabOrRelease()

	if not STATE.active then
		return
	end

	if STATE.magnetActive then
		return
	end

	if STATE.rotationMode then
		return
	end

	--------------------------------------------------
	-- SOLTAR
	--------------------------------------------------

	if STATE.holding then

		local target =
			STATE.target

		STATE.holding = false
		STATE.target = nil
		STATE.anchoredWhileHolding = false

		disconnectPhysics()

		if target
			and target:IsDescendantOf(workspace) then

			target.AssemblyLinearVelocity =
				Vector3.zero

			target.AssemblyAngularVelocity =
				Vector3.zero
		end

		if isMobile then
			updateMobileNormalUI()
		end

		return
	end

	--------------------------------------------------
	-- AGARRAR
	--------------------------------------------------

	local hit, position =
		raycastNormal()

	if not hit
		or not hit:IsA("BasePart") then

		return
	end

	if isPlayerPart(hit) then
		return
	end

	if hit.Anchored then

		if not hit:GetAttribute(
			"TelekinesisAnchored"
		) then
			return
		end

		hit.Anchored = false

		hit:SetAttribute(
			"TelekinesisAnchored",
			nil
		)
	end

	STATE.target = hit
	STATE.holding = true
	STATE.anchoredWhileHolding = false

	STATE.holdDistance =
		position
		and (
			camera.CFrame.Position
			- position
		).Magnitude
		or (
			camera.CFrame.Position
			- hit.Position
		).Magnitude

	bodyGyro.CFrame =
		hit.CFrame

	connectPhysics(hit)

	if isMobile then
		updateMobileNormalUI()
	end

	notify(
		"✋ AGARRADO",
		"",
		0.5
	)
end

--------------------------------------------------
-- LANZAR
--------------------------------------------------

local function throwObject()

	if not STATE.active
		or STATE.magnetActive
		or STATE.rotationMode then
		return
	end

	if not STATE.holding
		or not STATE.target then
		return
	end

	local target =
		STATE.target

	STATE.holding = false
	STATE.target = nil
	STATE.anchoredWhileHolding = false

	disconnectPhysics()

	if target
		and target:IsDescendantOf(workspace) then

		target.Anchored = false

		local ray =
			getAimRay()

		local velocity =
			Instance.new("BodyVelocity")

		velocity.Name =
			"TelekinesisThrow"

		velocity.MaxForce =
			Vector3.new(1, 1, 1) * 1e6

		velocity.P =
			CONFIG.holdVelocityP

		velocity.Velocity =
			ray.Direction.Unit
			* CONFIG.throwForce

		velocity.Parent =
			target

		Debris:AddItem(
			velocity,
			0.5
		)
	end

	if isMobile then
		updateMobileNormalUI()
	end

	notify(
		"🔥 LANZADO",
		"",
		0.5
	)
end

--------------------------------------------------
-- ANCLAR
--------------------------------------------------

local function toggleAnchored()

	if not STATE.active
		or STATE.magnetActive
		or STATE.rotationMode then
		return
	end

	if not STATE.holding
		or not STATE.target then
		return
	end

	local target =
		STATE.target

	STATE.anchoredWhileHolding =
		not STATE.anchoredWhileHolding

	target.Anchored =
		STATE.anchoredWhileHolding

	if STATE.anchoredWhileHolding then

		target:SetAttribute(
			"TelekinesisAnchored",
			true
		)

		target.AssemblyLinearVelocity =
			Vector3.zero

		target.AssemblyAngularVelocity =
			Vector3.zero

		disconnectPhysics()

	else

		target:SetAttribute(
			"TelekinesisAnchored",
			nil
		)

		connectPhysics(target)
	end

	notify(
		STATE.anchoredWhileHolding
			and "📌 ANCLADO"
			or "📎 DESANCLADO",
		"",
		0.5
	)
end

--------------------------------------------------
-- ROTACIÓN
--------------------------------------------------

local function resetRotationKeys()

	STATE.rotationKeys.up = false
	STATE.rotationKeys.down = false
	STATE.rotationKeys.left = false
	STATE.rotationKeys.right = false
end

local function enterRotationMode()

	if not STATE.holding
		or not STATE.target then

		notify(
			"❌ NO DISPONIBLE",
			"Debes agarrar un objeto primero",
			2
		)

		return
	end

	if STATE.anchoredWhileHolding then

		notify(
			"❌ OBJETO ANCLADO",
			"Desancla el objeto antes de rotarlo",
			2
		)

		return
	end

	local target =
		STATE.target

	STATE.rotationMode =
		true

	STATE.rotationPosition =
		target.Position

	resetRotationKeys()

	bodyVelocity.Parent = nil
	bodyGyro.Parent = nil

	rotationPositionLock.Position =
		STATE.rotationPosition

	rotationPositionLock.Parent =
		target

	target.AssemblyLinearVelocity =
		Vector3.zero

	target.AssemblyAngularVelocity =
		Vector3.zero

	freezeCharacter()
	hideCharacter()

	if isMobile then
		updateMobileRotationUI()
	else
		if UI.rotationLabel then
			UI.rotationLabel.Visible =
				STATE.guiVisible
		end

		if UI.forceLabel then
			UI.forceLabel.Visible = false
		end
	end

	notify(
		"🔄 MODO ROTACIÓN",
		"W/A/S/D o flechas",
		1.5
	)
end

local function exitRotationMode()

	if not STATE.rotationMode then
		return
	end

	local target =
		STATE.target

	STATE.rotationMode =
		false

	resetRotationKeys()

	rotationPositionLock.Parent =
		nil

	STATE.rotationPosition =
		nil

	showCharacter()
	unfreezeCharacter()

	if target
		and target:IsDescendantOf(workspace) then

		if not STATE.anchoredWhileHolding then

			connectPhysics(target)

			bodyGyro.CFrame =
				target.CFrame
		end
	end

	if isMobile then
		updateMobileNormalUI()
	else
		if UI.rotationLabel then
			UI.rotationLabel.Visible =
				STATE.guiVisible
				and false
		end

		if UI.forceLabel
			and STATE.guiVisible then

			UI.forceLabel.Visible = true
		end
	end

	notify(
		"🔄 ROTACIÓN TERMINADA",
		"",
		0.7
	)
end

local function toggleRotationMode()

	if STATE.rotationMode then
		exitRotationMode()
	else
		enterRotationMode()
	end
end

--------------------------------------------------
-- ACTUALIZAR ROTACIÓN
--------------------------------------------------

local function updateRotation()

	if not STATE.rotationMode
		or not STATE.target then
		return
	end

	local target =
		STATE.target

	if STATE.rotationPosition then

		rotationPositionLock.Position =
			STATE.rotationPosition
	end

	local horizontal = 0
	local vertical = 0

	if STATE.rotationKeys.left then
		horizontal -= 1
	end

	if STATE.rotationKeys.right then
		horizontal += 1
	end

	if STATE.rotationKeys.up then
		vertical += 1
	end

	if STATE.rotationKeys.down then
		vertical -= 1
	end

	if horizontal == 0
		and vertical == 0 then

		target.AssemblyLinearVelocity =
			Vector3.zero

		target.AssemblyAngularVelocity =
			Vector3.zero

		return
	end

	if horizontal ~= 0 then

		target.CFrame =
			target.CFrame
			* CFrame.Angles(
				0,
				math.rad(
					horizontal
					* CONFIG.rotationSpeed
				),
				0
			)
	end

	if vertical ~= 0 then

		target.CFrame =
			target.CFrame
			* CFrame.Angles(
				math.rad(
					-vertical
					* CONFIG.rotationSpeed
				),
				0,
				0
			)
	end

	target.AssemblyLinearVelocity =
		Vector3.zero

	target.AssemblyAngularVelocity =
		Vector3.zero
end

--------------------------------------------------
-- IMÁN
--------------------------------------------------

local function restoreMagnetPhysics()

	for object in pairs(
		STATE.magnetObjects
	) do

		if object
			and object:IsDescendantOf(workspace) then

			local bv =
				object:FindFirstChild("ImanBV")

			if bv then
				bv:Destroy()
			end
		end
	end

	STATE.magnetObjects = {}
end

local function massAnchorMagnet()

	if not STATE.active
		or not STATE.magnetActive then
		return
	end

	local count = 0

	for object in pairs(
		STATE.magnetObjects
	) do

		if object
			and object:IsDescendantOf(workspace) then

			object.Anchored = true

			object:SetAttribute(
				"TelekinesisAnchored",
				true
			)

			local bv =
				object:FindFirstChild("ImanBV")

			if bv then
				bv:Destroy()
			end

			count += 1
		end
	end

	STATE.magnetObjects = {}

	notify(
		"📌 ANCLAJE MASIVO",
		"Se anclaron "
			.. count
			.. " objetos",
		1.5
	)
end

local function activateMagnet()

	if not STATE.active then
		return
	end

	if STATE.holding then

		notify(
			"❌ NO DISPONIBLE",
			"Suelta el objeto para usar el imán",
			2
		)

		return
	end

	if STATE.rotationMode then
		return
	end

	STATE.magnetActive = true

	if isMobile then
		updateMobileMagnetUI()
	end

	notify(
		"🧲 IMÁN ACTIVADO",
		"Las piezas no ancladas se atraviesan",
		1.5
	)
end

local function deactivateMagnet()

	STATE.magnetActive = false

	restoreMagnetPhysics()

	if isMobile then
		updateMobileNormalUI()
	end

	notify(
		"🧲 IMÁN DESACTIVADO",
		"",
		0.7
	)
end

local function toggleMagnet()

	if STATE.magnetActive then
		deactivateMagnet()
	else
		activateMagnet()
	end
end

--------------------------------------------------
-- FUERZA
--------------------------------------------------

local function changeThrowForce(amount)

	CONFIG.throwForce =
		math.clamp(
			CONFIG.throwForce + amount,
			CONFIG.throwForceMin,
			CONFIG.throwForceMax
		)

	if isMobile
		and STATE.mobileMode == "normal" then

		updateMobileNormalUI()
	end

	notify(
		"🎯 FUERZA",
		tostring(
			math.floor(
				CONFIG.throwForce
			)
		),
		0.5
	)
end

local function changeMagnetForce(amount)

	CONFIG.magnetForce =
		math.clamp(
			CONFIG.magnetForce + amount,
			CONFIG.magnetForceMin,
			CONFIG.magnetForceMax
		)

	if isMobile
		and STATE.mobileMode == "magnet" then

		updateMobileMagnetUI()
	end
end

local function changeMagnetRadius(amount)

	CONFIG.magnetRadius =
		math.clamp(
			CONFIG.magnetRadius + amount,
			CONFIG.magnetRadiusMin,
			CONFIG.magnetRadiusMax
		)

	if isMobile
		and STATE.mobileMode == "magnet" then

		updateMobileMagnetUI()
	end
end

--------------------------------------------------
-- DISTANCIA
--------------------------------------------------

local function updateDistance(delta)

	if STATE.rotationMode then
		return
	end

	if STATE.magnetActive then

		changeMagnetRadius(delta)

		return
	end

	if not STATE.holding then
		return
	end

	STATE.holdDistance =
		math.clamp(
			STATE.holdDistance + delta,
			CONFIG.holdDistanceMin,
			CONFIG.holdDistanceMax
		)
end

--------------------------------------------------
-- PC INPUT
--------------------------------------------------

UserInputService.InputBegan:Connect(
	function(input, gameProcessed)

		if not STATE.active
			or gameProcessed then
			return
		end

		if input.UserInputType ==
			Enum.UserInputType.Keyboard then

			local key =
				input.KeyCode

			--------------------------------------------------
			-- ROTACIÓN
			--------------------------------------------------

			if STATE.rotationMode then

				if key ==
					Enum.KeyCode.C then

					exitRotationMode()
					return
				end

				if key ==
					Enum.KeyCode.W
					or key ==
					Enum.KeyCode.Up then

					STATE.rotationKeys.up = true
					return
				end

				if key ==
					Enum.KeyCode.S
					or key ==
					Enum.KeyCode.Down then

					STATE.rotationKeys.down = true
					return
				end

				if key ==
					Enum.KeyCode.A
					or key ==
					Enum.KeyCode.Left then

					STATE.rotationKeys.left = true
					return
				end

				if key ==
					Enum.KeyCode.D
					or key ==
					Enum.KeyCode.Right then

					STATE.rotationKeys.right = true
					return
				end

				return
			end

			--------------------------------------------------
			-- NORMAL
			--------------------------------------------------

			if key == Enum.KeyCode.Q then

				STATE.holdQ = true

			elseif key == Enum.KeyCode.E then

				STATE.holdE = true

			elseif key == Enum.KeyCode.R then

				if STATE.magnetActive then
					massAnchorMagnet()
				else
					toggleAnchored()
				end

			elseif key == Enum.KeyCode.F then

				throwObject()

			elseif key == Enum.KeyCode.C then

				toggleRotationMode()

			elseif key == Enum.KeyCode.T then

				toggleMagnet()

			elseif key == Enum.KeyCode.Z then

				if STATE.magnetActive then

					changeMagnetForce(
						-CONFIG.magnetForceStep
					)

				else

					changeThrowForce(
						-CONFIG.throwForceStepPC
					)
				end

			elseif key == Enum.KeyCode.X then

				if STATE.magnetActive then

					changeMagnetForce(
						CONFIG.magnetForceStep
					)

				else

					changeThrowForce(
						CONFIG.throwForceStepPC
					)
				end

			elseif key == Enum.KeyCode.RightControl then

				toggleInterface()

			elseif key == Enum.KeyCode.LeftControl then

				notify(
					"⌨️ CONTROLES",
					"Click = Agarrar\n"
						.. "F = Lanzar\n"
						.. "Q/E = Distancia\n"
						.. "R = Anclar\n"
						.. "T = Imán\n"
						.. "C = Rotación\n"
						.. "Ctrl derecho = Mostrar/Ocultar",
					7
				)

			elseif key == Enum.KeyCode.LeftAlt
				or key == Enum.KeyCode.RightAlt then

				notify(
					"⌨️ ROTACIÓN",
					"W/A/S/D o flechas",
					2
				)
			end

		elseif input.UserInputType ==
			Enum.UserInputType.MouseButton1 then

			grabOrRelease()
		end
	end
)

--------------------------------------------------
-- PC INPUT END
--------------------------------------------------

UserInputService.InputEnded:Connect(
	function(input)

		if not STATE.active then
			return
		end

		if input.UserInputType ==
			Enum.UserInputType.Keyboard then

			local key =
				input.KeyCode

			if STATE.rotationMode then

				if key ==
					Enum.KeyCode.W
					or key ==
					Enum.KeyCode.Up then

					STATE.rotationKeys.up = false

				elseif key ==
					Enum.KeyCode.S
					or key ==
					Enum.KeyCode.Down then

					STATE.rotationKeys.down = false

				elseif key ==
					Enum.KeyCode.A
					or key ==
					Enum.KeyCode.Left then

					STATE.rotationKeys.left = false

				elseif key ==
					Enum.KeyCode.D
					or key ==
					Enum.KeyCode.Right then

					STATE.rotationKeys.right = false
				end

				return
			end

			if key == Enum.KeyCode.Q then
				STATE.holdQ = false

			elseif key == Enum.KeyCode.E then
				STATE.holdE = false
			end
		end
	end
)

--------------------------------------------------
-- MÓVIL: HELPERS
--------------------------------------------------

local function connectTap(
	button,
	callback
)

	button.Activated:Connect(
		function()

			if not STATE.active then
				return
			end

			callback()
		end
	)
end

local function connectHold(
	button,
	onBegin,
	onEnd
)

	local activeInput = nil

	button.InputBegan:Connect(
		function(input)

			if not STATE.active then
				return
			end

			if input.UserInputType ==
				Enum.UserInputType.Touch
				or input.UserInputType ==
				Enum.UserInputType.MouseButton1 then

				activeInput = input
				onBegin()
			end
		end
	)

	button.InputEnded:Connect(
		function(input)

			if input == activeInput then

				activeInput = nil
				onEnd()
			end
		end
	)
end

--------------------------------------------------
-- MÓVIL: BOTONES
--------------------------------------------------

if isMobile
	and UI.buttons then

	local b = UI.buttons

	--------------------------------------------------
	-- AGARRAR
	--------------------------------------------------

	connectTap(
		UI.actionButton,
		function()
			grabOrRelease()
		end
	)

	--------------------------------------------------
	-- LANZAR
	--------------------------------------------------

	connectTap(
		b.throw,
		function()
			throwObject()
		end
	)

	--------------------------------------------------
	-- ANCLAR
	--------------------------------------------------

	connectTap(
		b.anchor,
		function()
			toggleAnchored()
		end
	)

	--------------------------------------------------
	-- DISTANCIA
	--------------------------------------------------

	connectHold(
		b.distanceDown,

		function()
			STATE.holdClose = true
		end,

		function()
			STATE.holdClose = false
		end
	)

	connectHold(
		b.distanceUp,

		function()
			STATE.holdFar = true
		end,

		function()
			STATE.holdFar = false
		end
	)

	--------------------------------------------------
	-- FUERZA
	--------------------------------------------------

	connectTap(
		b.forceDown,
		function()
			changeThrowForce(
				-CONFIG.throwForceStepMobile
			)
		end
	)

	connectTap(
		b.forceUp,
		function()
			changeThrowForce(
				CONFIG.throwForceStepMobile
			)
		end
	)

	--------------------------------------------------
	-- IMÁN
	--------------------------------------------------

	connectTap(
		b.magnet,
		function()
			activateMagnet()
		end
	)

	--------------------------------------------------
	-- ROTACIÓN
	--------------------------------------------------

	connectTap(
		b.rotation,
		function()
			enterRotationMode()
		end
	)

	--------------------------------------------------
	-- OJO
	--------------------------------------------------

	connectTap(
		UI.hideButton,
		function()
			setInterfaceVisible(false)
		end
	)

	--------------------------------------------------
	-- IMÁN
	--------------------------------------------------

	local mb =
		UI.magnetButtons

	connectTap(
		mb.forceDown,
		function()
			changeMagnetForce(
				-CONFIG.magnetForceStep
			)
		end
	)

	connectTap(
		mb.forceUp,
		function()
			changeMagnetForce(
				CONFIG.magnetForceStep
			)
		end
	)

	connectTap(
		mb.radiusDown,
		function()
			changeMagnetRadius(
				-CONFIG.magnetRadiusStep
			)
		end
	)

	connectTap(
		mb.radiusUp,
		function()
			changeMagnetRadius(
				CONFIG.magnetRadiusStep
			)
		end
	)

	connectTap(
		mb.massAnchor,
		function()
			massAnchorMagnet()
		end
	)

	connectTap(
		mb.exit,
		function()
			deactivateMagnet()
		end
	)

	--------------------------------------------------
	-- ROTACIÓN
	--------------------------------------------------

	local rb =
		UI.rotationButtons

	connectHold(
		rb.up,

		function()
			STATE.rotationKeys.up = true
		end,

		function()
			STATE.rotationKeys.up = false
		end
	)

	connectHold(
		rb.down,

		function()
			STATE.rotationKeys.down = true
		end,

		function()
			STATE.rotationKeys.down = false
		end
	)

	connectHold(
		rb.left,

		function()
			STATE.rotationKeys.left = true
		end,

		function()
			STATE.rotationKeys.left = false
		end
	)

	connectHold(
		rb.right,

		function()
			STATE.rotationKeys.right = true
		end,

		function()
			STATE.rotationKeys.right = false
		end
	)

	connectTap(
		rb.exit,
		function()
			exitRotationMode()
		end
	)
end

--------------------------------------------------
-- BOTÓN DE MOSTRAR MÓVIL
--
-- ES UN SOLO BOTÓN PERSISTENTE
-- ARRIBA A LA IZQUIERDA.
--------------------------------------------------

if isMobile then

	local showButton =
		Instance.new("TextButton")

	showButton.Name =
		"ShowInterface"

	showButton.Size =
		UDim2.fromOffset(55, 55)

	showButton.Position =
		UDim2.fromOffset(15, 15)

	showButton.BackgroundColor3 =
		Color3.fromRGB(35, 35, 35)

	showButton.BackgroundTransparency =
		0.2

	showButton.Text =
		"👁️"

	showButton.TextScaled =
		true

	showButton.Font =
		Enum.Font.GothamBold

	showButton.TextColor3 =
		Color3.new(1, 1, 1)

	showButton.Visible =
		false

	showButton.ZIndex =
		200

	styleButton(showButton)

	showButton.Parent =
		UI.mobileGui

	UI.showButton =
		showButton

	showButton.Activated:Connect(
		function()

			setInterfaceVisible(true)

			showButton.Visible =
				false

			if STATE.mobileMode ==
				"magnet"
				and STATE.magnetActive then

				updateMobileMagnetUI()

			elseif STATE.mobileMode ==
				"rotation"
				and STATE.rotationMode then

				updateMobileRotationUI()

			else

				updateMobileNormalUI()
			end
		end
	)

	--------------------------------------------------
	-- SOBREESCRIBIR OCULTAR:
	-- Mostrar botón arriba izquierda.
	--------------------------------------------------

	UI.hideButton.Activated:Connect(
		function()

			setInterfaceVisible(false)

			showButton.Visible =
				true
		end
	)
end

--------------------------------------------------
-- CURSOR MÓVIL
--------------------------------------------------

if isMobile
	and UI.fakePointer then

	UI.fakePointer.InputBegan:Connect(
		function(input)

			if input.UserInputType
				~= Enum.UserInputType.Touch then

				return
			end

			STATE.pointerTouch =
				input

			STATE.pointerTouchStart =
				input.Position

			STATE.pointerDragStarted =
				false
		end
	)

	UserInputService.InputChanged:Connect(
		function(input)

			if input.UserInputType
				~= Enum.UserInputType.Touch then

				return
			end

			if input ~=
				STATE.pointerTouch then

				return
			end

			if not STATE.pointerTouchStart then
				return
			end

			local delta =
				input.Position
				- STATE.pointerTouchStart

			if not STATE.pointerDragStarted then

				if delta.Magnitude
					< CONFIG.pointerDragThreshold then

					return
				end

				STATE.pointerDragStarted =
					true
			end

			UI.fakePointer.Position =
				UDim2.fromOffset(
					input.Position.X,
					input.Position.Y
				)
		end
	)

	UserInputService.InputEnded:Connect(
		function(input)

			if input ~=
				STATE.pointerTouch then

				return
			end

			STATE.pointerTouch = nil
			STATE.pointerTouchStart = nil
			STATE.pointerDragStarted = false
		end
	)

	UserInputService.TouchEnded:Connect(
		function(input)

			if input ~=
				STATE.pointerTouch then

				return
			end

			STATE.pointerTouch = nil
			STATE.pointerTouchStart = nil
			STATE.pointerDragStarted = false
		end
	)
end

--------------------------------------------------
-- RENDER
--------------------------------------------------

RunService.RenderStepped:Connect(
	function()

		if not STATE.active then
			return
		end

		--------------------------------------------------
		-- ROTACIÓN
		--------------------------------------------------

		updateRotation()

		--------------------------------------------------
		-- HIGHLIGHTS
		--------------------------------------------------

		refreshHighlights()

		--------------------------------------------------
		-- IMÁN VISUAL
		--------------------------------------------------

		if STATE.magnetActive
			and not STATE.rotationMode
			and STATE.guiVisible then

			local hit, position =
				raycastForMagnet()

			if hit and position then

				surfacePosition =
					surfacePosition:Lerp(
						position,
						0.35
					)

			else

				local ray =
					getAimRay()

				surfacePosition =
					surfacePosition:Lerp(
						ray.Origin
							+ ray.Direction * 15,
						0.15
					)
			end

			actualMagnetRadius +=
				(
					CONFIG.magnetRadius
					- actualMagnetRadius
				) * 0.25

			local percent =
				(
					CONFIG.magnetForce
					- CONFIG.magnetForceMin
				)
				/
				(
					CONFIG.magnetForceMax
					- CONFIG.magnetForceMin
				)

			local colorScale =
				Color3.fromHSV(
					0.58
						+ percent * 0.3,
					1,
					1
				)

			if usingDrawing
				and magnetCircle then

				local viewport =
					camera:WorldToViewportPoint(
						surfacePosition
					)

				magnetCircle.Visible =
					true

				magnetCircle.Position =
					Vector2.new(
						viewport.X,
						viewport.Y
					)

				magnetCircle.Radius =
					actualMagnetRadius

				magnetCircle.Color =
					colorScale

			elseif circleGui then

				circleGui.Enabled =
					true

				circleGui.Size =
					UDim2.fromOffset(
						actualMagnetRadius * 2,
						actualMagnetRadius * 2
					)

				circleGui.Position =
					surfacePosition

				circleImage.ImageColor3 =
					colorScale
			end

			if UI.forceLabel
				and STATE.guiVisible
				and not isMobile then

				UI.forceLabel.Visible =
					true

				UI.forceLabel.Text =
					"🧲 Fuerza: "
					.. math.floor(
						CONFIG.magnetForce
					)
					.. "\n📏 Radio: "
					.. math.floor(
						CONFIG.magnetRadius
					)
			end

		elseif not STATE.rotationMode
			and STATE.guiVisible then

			if usingDrawing
				and magnetCircle then

				magnetCircle.Visible = false

			elseif circleGui then

				circleGui.Enabled = false
			end

			if UI.forceLabel
				and not isMobile then

				UI.forceLabel.Visible =
					true

				UI.forceLabel.Text =
					"🎯 Lanzamiento: "
					.. math.floor(
						CONFIG.throwForce
					)
			end

		else

			if usingDrawing
				and magnetCircle then

				magnetCircle.Visible = false

			elseif circleGui then

				circleGui.Enabled = false
			end

			if UI.forceLabel then
				UI.forceLabel.Visible =
					false
			end
		end

		--------------------------------------------------
		-- DISTANCIA
		--------------------------------------------------

		if STATE.holding
			and not STATE.rotationMode
			and not STATE.magnetActive then

			if STATE.holdClose then

				updateDistance(
					-CONFIG.distanceSpeed
				)
			end

			if STATE.holdFar then

				updateDistance(
					CONFIG.distanceSpeed
				)
			end
		end
	end
)

--------------------------------------------------
-- HEARTBEAT
--------------------------------------------------

RunService.Heartbeat:Connect(
	function()

		if not STATE.active then
			return
		end

		--------------------------------------------------
		-- OBJETO
		--------------------------------------------------

		if STATE.holding
			and STATE.target then

			local target =
				STATE.target

			if STATE.rotationMode then

				target.AssemblyLinearVelocity =
					Vector3.zero

				target.AssemblyAngularVelocity =
					Vector3.zero

				if STATE.rotationPosition then

					rotationPositionLock.Position =
						STATE.rotationPosition
				end

			elseif not STATE.anchoredWhileHolding then

				local ray =
					getAimRay()

				local desiredPosition =
					ray.Origin
					+ ray.Direction.Unit
					* STATE.holdDistance

				bodyVelocity.Velocity =
					(
						desiredPosition
						- target.Position
					) * 5

				bodyGyro.CFrame =
					target.CFrame

			else

				target.AssemblyLinearVelocity =
					Vector3.zero

				target.AssemblyAngularVelocity =
					Vector3.zero
			end
		end

		--------------------------------------------------
		-- IMÁN
		--------------------------------------------------

		if STATE.magnetActive
			and not STATE.rotationMode then

			local center =
				surfacePosition

			local newObjects = {}

			local scaledForce =
				CONFIG.magnetForce
				* (
					CONFIG.magnetRadius / 30
				) ^ 1.5

			local diameter =
				CONFIG.magnetRadius * 2

			local nearby =
				workspace:GetPartBoundsInBox(
					CFrame.new(center),
					Vector3.new(
						diameter,
						diameter,
						diameter
					)
				)

			for _, object in ipairs(
				nearby
			) do

				if object:IsA("BasePart")
					and not object.Anchored
					and not isPlayerPart(object)
					and not hasUnanchoredWeld(object)
					and object.Transparency < 1
					and object.CanCollide
					and object ~= STATE.target then

					local offset =
						center - object.Position

					local distance =
						offset.Magnitude

					if distance
						<= CONFIG.magnetRadius
						and distance > 0.01 then

						local velocity =
							object:FindFirstChild(
								"ImanBV"
							)

						if not velocity then

							velocity =
								Instance.new(
									"BodyVelocity"
								)

							velocity.Name =
								"ImanBV"

							velocity.MaxForce =
								Vector3.new(
									1,
									1,
									1
								) * 1e5

							velocity.P =
								15000

							velocity.Parent =
								object
						end

						velocity.Velocity =
							offset.Unit
							* scaledForce

						newObjects[object] =
							true
					end
				end
			end

			for object in pairs(
				STATE.magnetObjects
			) do

				if not newObjects[object] then

					local bv =
						object
						and object:FindFirstChild(
							"ImanBV"
						)

					if bv then
						bv:Destroy()
					end
				end
			end

			STATE.magnetObjects =
				newObjects

		else

			restoreMagnetPhysics()
		end

		--------------------------------------------------
		-- Q / E PC
		--------------------------------------------------

		if not STATE.rotationMode then

			if STATE.magnetActive then

				if STATE.holdQ then

					changeMagnetRadius(
						-CONFIG.magnetRadiusStep
					)
				end

				if STATE.holdE then

					changeMagnetRadius(
						CONFIG.magnetRadiusStep
					)
				end

			else

				if STATE.holdQ then

					updateDistance(
						-CONFIG.distanceKeySpeed
					)
				end

				if STATE.holdE then

					updateDistance(
						CONFIG.distanceKeySpeed
					)
				end
			end
		end
	end
)

--------------------------------------------------
-- RESPAWN
--------------------------------------------------

player.CharacterAdded:Connect(
	function()

		STATE.characterFrozen = false
		STATE.characterBackup = nil
		STATE.characterVisibilityBackup = {}

		if STATE.rotationMode then

			STATE.rotationMode = false

			resetRotationKeys()

			rotationPositionLock.Parent = nil
		end
	end
)

--------------------------------------------------
-- INICIO
--------------------------------------------------

if isMobile then

	updateMobileNormalUI()

	UI.showButton.Visible = false

else

	if UI.forceLabel then
		UI.forceLabel.Visible = true
	end
end

print(
	"[Telekinesis V4.4] "
	.. "UI toggle corregido + "
	.. "Ctrl derecho + "
	.. "layouts de modos ajustados"
)

Features & Information

# 🧲 Script de Telequinesis para Roblox ## Descripción Breve Script avanzado de telequinesis para Roblox que permite controlar objetos con poderes mentales. Compatible con PC y móvil mediante un sistema unificado. ## 🎮 Funcionalidades Principales Agarrar y Mover Objetos - Mantén objetos suspendidos y controla su distancia Lanzar Objetos - Lanza objetos en la dirección que apuntas Modo Imán - Atrae múltiples objetos sueltos dentro de un radio configurable y atraviesa piezas no ancladas al apuntar Rotar Objetos - Gira objetos 360° manteniéndolos fijos en su posición Anclar Objetos - Congela objetos en el aire Anclaje Masivo - Ancla todos los objetos atraídos por el imán ## ⌨️ Controles PC Click Izquierdo - Agarrar/Soltar F - Lanzar Q/E - Acercar/Alejar R - Anclar/Desanclar T - Activar/Desactivar Imán C - Modo Rotación Z/X - Bajar/Subir Fuerza W/A/S/D o Flechas - Rotar Ctrl Derecho - Mostrar/Ocultar interfaz ## 📱 Compatible Móvil Interfaz táctil completa con cursor visual y controles que cambian según el modo activo. Incluye controles de agarrar, lanzar, anclar, distancia, fuerza, imán y rotación. ## ✨ Características Avanzadas Highlights visuales Círculo visual del imán Fuerza y radio configurables Personaje invisible durante la rotación Bloqueo de posición al rotar Interfaz adaptable por modos

Community Comments (1)

nathanblox65nathanblox65Jan 3, 2026

bom

More Scripts for Universal Scripts