local RS = game:GetService("ReplicatedStorage") local RunService = game:GetService("RunService") local UIS = game:GetService("UserInputService") local CAS = game:GetService("ContextActionService") local TweenService = game:GetService("TweenService") local Players = game:GetService("Players") local plr = Players.LocalPlayer local cam = workspace.CurrentCamera if shared.NexusVRPC_Cleanup then pcall(shared.NexusVRPC_Cleanup) end local remote pcall(function() local m = RS:WaitForChild("NexusVRCharacterModel", 8) remote = m:WaitForChild("UpdateInputs", 8) local ready = m:FindFirstChild("ReplicationReady") if ready then ready:FireServer() end end) local CFG = { sendRate = 1 / 30, smooth = 22, reach = 3.1, reachLong = 14, depth = 2.2, depthMin = 0.6, depthMax = 40, gripWidth = 0.9, rollSpeed = 3.5, maxRecord = 30, } local S = { enabled = true, autoReturn = true, headLook = false, longReach = false, noclip = true, hud = true, help = false, recording = false, playing = false, } local sin, cos, exp, clamp, abs = math.sin, math.cos, math.exp, math.clamp, math.abs local V3 = Vector3.new local DOWN = V3(0, -1, 0) local conns = {} local function bind(signal, fn) conns[#conns + 1] = signal:Connect(fn) end local function newHand(sign) local rest = V3(sign * 1.0, -0.7, -0.6) return { sign = sign, shoulder = V3(sign * 1.0, 0.6, 0), rest = rest, pos = rest, vel = V3(0, 0, 0), target = rest, roll = 0, rollTarget = 0, depth = CFG.depth, pinned = false, cf = CFrame.new(rest), dir = DOWN, } end local hands = {L = newHand(-1), R = newHand(1)} local currentMode = nil local headSmooth = nil local state = {valid = false, rootCF = CFrame.new(), headCF = CFrame.new(), dirL = DOWN, dirR = DOWN} local slots = {} local rec = {frames = {}, t = 0} local play = {t = 0, i = 1} local function make(class, props, parent) local o = Instance.new(class) for k, v in pairs(props) do o[k] = v end o.Parent = parent return o end local function getParent() if gethui then local ok, g = pcall(gethui) if ok and g then return g end end local ok, core = pcall(function() return game:GetService("CoreGui") end) if ok and core then local tested = pcall(function() local t = Instance.new("Folder") t.Parent = core t:Destroy() end) if tested then return core end end return plr:WaitForChild("PlayerGui") end local parentGui = getParent() if parentGui:FindFirstChild("NexusVR_PC") then parentGui.NexusVR_PC:Destroy() end local gui = make("ScreenGui", { Name = "NexusVR_PC", ResetOnSpawn = false, DisplayOrder = 99999, IgnoreGuiInset = true, }, parentGui) local panel = make("Frame", { Size = UDim2.fromOffset(260, 158), Position = UDim2.fromOffset(16, 16), BackgroundColor3 = Color3.fromRGB(12, 14, 20), BackgroundTransparency = 0.15, BorderSizePixel = 0, }, gui) make("UICorner", {CornerRadius = UDim.new(0, 10)}, panel) make("UIStroke", {Color = Color3.fromRGB(0, 200, 255), Transparency = 0.5, Thickness = 1.2}, panel) local statusLabel = make("TextLabel", { Size = UDim2.new(1, -20, 1, -14), Position = UDim2.fromOffset(10, 7), BackgroundTransparency = 1, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Top, RichText = true, Font = Enum.Font.Code, TextSize = 13, TextColor3 = Color3.fromRGB(225, 235, 245), Text = "", }, panel) local helpPanel = make("Frame", { Size = UDim2.fromOffset(330, 262), Position = UDim2.fromOffset(16, 182), BackgroundColor3 = Color3.fromRGB(12, 14, 20), BackgroundTransparency = 0.15, BorderSizePixel = 0, Visible = false, }, gui) make("UICorner", {CornerRadius = UDim.new(0, 10)}, helpPanel) make("UIStroke", {Color = Color3.fromRGB(0, 200, 255), Transparency = 0.5, Thickness = 1.2}, helpPanel) local helpLines = { "CONTROLES", "Q / E mano izq / der sigue el cursor", "G agarre a dos manos (Shift+rueda: ancho)", "F modo espejo", "Rueda profundidad de la mano activa", "Z / C girar muñeca", "1-3 cargar pose Ctrl+1-3 guardar", "R grabar P reproducir / parar", "T mantener posición / retorno auto", "L cabeza sigue el cursor", "X alcance largo", "[ ] suavidad", "N no-clip jugadores", "V VR on/off Y reiniciar", "H esta ayuda U ocultar HUD", } make("TextLabel", { Size = UDim2.new(1, -20, 1, -14), Position = UDim2.fromOffset(10, 7), BackgroundTransparency = 1, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Top, RichText = true, Font = Enum.Font.Code, TextSize = 13, TextColor3 = Color3.fromRGB(225, 235, 245), Text = table.concat(helpLines, "\n"), }, helpPanel) local function makeMarker(color, letter) local f = make("Frame", { Size = UDim2.fromOffset(22, 22), AnchorPoint = Vector2.new(0.5, 0.5), BackgroundColor3 = color, BackgroundTransparency = 0.55, BorderSizePixel = 0, Visible = false, }, gui) make("UICorner", {CornerRadius = UDim.new(1, 0)}, f) make("UIStroke", {Color = color, Thickness = 2, Transparency = 0.1}, f) make("TextLabel", { Size = UDim2.fromScale(1, 1), BackgroundTransparency = 1, Text = letter, TextColor3 = Color3.new(1, 1, 1), TextSize = 12, Font = Enum.Font.GothamBold, }, f) return f end local markerL = makeMarker(Color3.fromRGB(255, 80, 80), "I") local markerR = makeMarker(Color3.fromRGB(70, 140, 255), "D") local toast = make("TextLabel", { Size = UDim2.fromOffset(380, 30), AnchorPoint = Vector2.new(0.5, 1), Position = UDim2.new(0.5, 0, 1, -60), BackgroundTransparency = 1, TextColor3 = Color3.new(1, 1, 1), TextStrokeTransparency = 1, TextTransparency = 1, Font = Enum.Font.GothamBold, TextSize = 16, Text = "", }, gui) local toastId = 0 local function notify(text) toastId = toastId + 1 local id = toastId toast.Text = text toast.TextTransparency = 0 toast.TextStrokeTransparency = 0.4 task.delay(1.6, function() if id == toastId and toast.Parent then TweenService:Create(toast, TweenInfo.new(0.5), {TextTransparency = 1, TextStrokeTransparency = 1}):Play() end end) end local function modeName(m) if m == "L" then return "mano izquierda" elseif m == "R" then return "mano derecha" elseif m == "both" then return "agarre dos manos" elseif m == "mirror" then return "espejo" end return "libre" end local function refreshStatus() local on = S.enabled and 'ON' or 'OFF' local lines = { "NEXUS VR · PC " .. on, "Modo: " .. modeName(currentMode), string.format("Profundidad: I %.1f · D %.1f", hands.L.depth, hands.R.depth), string.format("Suavidad: %.0f · Agarre: %.1f", CFG.smooth, CFG.gripWidth), "Manos: " .. (S.autoReturn and "retorno auto" or "mantener"), "Cabeza: " .. (S.headLook and "sigue cursor" or "cámara"), "Alcance: " .. (S.longReach and "largo" or "normal"), } if S.recording then lines[#lines + 1] = string.format('● REC %.1fs', rec.t) elseif S.playing then lines[#lines + 1] = '▶ reproduciendo' elseif not remote then lines[#lines + 1] = 'sin remote de Nexus VR' else lines[#lines + 1] = "H = ayuda" end statusLabel.Text = table.concat(lines, "\n") end local function rotBetween(from, to) local dot = clamp(from:Dot(to), -1, 1) local axis = from:Cross(to) if axis.Magnitude < 1e-4 then if dot > 0 then return CFrame.new() end return CFrame.fromAxisAngle(Vector3.xAxis, math.pi) end return CFrame.fromAxisAngle(axis.Unit, math.acos(dot)) end local joints = {} local function refreshJoints(char) if joints.char == char and joints.neck and joints.neck.Parent then return end joints.char = char joints.neck = char:FindFirstChild("Neck", true) joints.L = char:FindFirstChild("Left Shoulder", true) or char:FindFirstChild("LeftShoulder", true) joints.R = char:FindFirstChild("Right Shoulder", true) or char:FindFirstChild("RightShoulder", true) end local function applyJoints() if not S.enabled or not state.valid then return end local char = plr.Character if not char then return end refreshJoints(char) local n, l, r = joints.neck, joints.L, joints.R if n and n.Part0 then n.Transform = n.C0.Rotation:Inverse() * n.Part0.CFrame.Rotation:Inverse() * state.headCF.Rotation * n.C1.Rotation end if l and l.Part0 then local fr = l.Part0.CFrame * l.C0 l.Transform = rotBetween(DOWN, fr:VectorToObjectSpace(state.dirL)) end if r and r.Part0 then local fr = r.Part0.CFrame * r.C0 r.Transform = rotBetween(DOWN, fr:VectorToObjectSpace(state.dirR)) end end local function resetJoints() local char = plr.Character if not char then return end refreshJoints(char) for _, j in ipairs({joints.neck, joints.L, joints.R}) do if j then j.Transform = CFrame.new() end end end local function key(k) return UIS:IsKeyDown(k) and not UIS:GetFocusedTextBox() end local function getMode() if key(Enum.KeyCode.G) then return "both" end if key(Enum.KeyCode.F) then return "mirror" end local l, r = key(Enum.KeyCode.Q), key(Enum.KeyCode.E) if l and r then return "both" end if l then return "L" end if r then return "R" end return nil end local function clampReach(h, p) local v = p - h.shoulder local maxR = S.longReach and CFG.reachLong or CFG.reach if v.Magnitude > maxR then v = v.Unit * maxR end return h.shoulder + v end local function stepSpring(h, target, dt) local w = CFG.smooth local e = exp(-w * dt) local x = h.pos - target local tmp = h.vel + x * w h.pos = target + (x + tmp * dt) * e h.vel = (h.vel - tmp * w * dt) * e h.roll = h.roll + (h.rollTarget - h.roll) * (1 - exp(-w * dt)) end local function buildHandCF(h) local v = h.pos - h.shoulder local dir = v.Magnitude > 0.05 and v.Unit or DOWN h.dir = dir local up = abs(dir.Y) > 0.98 and V3(0, 0, -1) or Vector3.yAxis h.cf = CFrame.lookAt(h.pos, h.pos + dir, up) * CFrame.Angles(0, 0, h.roll) end local function savePose(n) slots[n] = { L = {pos = hands.L.pos, roll = hands.L.roll}, R = {pos = hands.R.pos, roll = hands.R.roll}, } notify("Pose " .. n .. " guardada") end local function loadPose(n) local s = slots[n] if not s then notify("Pose " .. n .. " vacía") return end for k, h in pairs(hands) do h.target = s[k].pos h.rollTarget = s[k].roll h.pinned = true end notify("Pose " .. n .. " cargada") end local function startRecording() rec.frames = {} rec.t = 0 S.recording = true S.playing = false notify("● Grabando (R para terminar)") end local function stopRecording() S.recording = false notify("Grabación lista: " .. #rec.frames .. " cuadros") end local function startPlayback() if #rec.frames < 2 then notify("No hay nada grabado") return end S.playing = true S.recording = false play.t = 0 play.i = 1 notify("▶ Reproduciendo (P para parar)") end local function sampleRecording(tm) local f = rec.frames local n = #f local i = play.i if i > n - 1 then i = 1 end while i < n - 1 and f[i + 1].t < tm do i = i + 1 end play.i = i local a, b = f[i], f[i + 1] local span = math.max(b.t - a.t, 1e-4) return a, b, clamp((tm - a.t) / span, 0, 1) end local function resetAll() for _, h in pairs(hands) do h.target = h.rest h.rollTarget = 0 h.depth = CFG.depth h.pinned = false end S.recording = false S.playing = false notify("Reiniciado") end local function toggleEnabled() S.enabled = not S.enabled if not S.enabled then state.valid = false resetJoints() end notify(S.enabled and "VR activado" or "VR desactivado") end bind(UIS.InputBegan, function(input) if input.UserInputType ~= Enum.UserInputType.Keyboard then return end if UIS:GetFocusedTextBox() then return end local k = input.KeyCode local ctrl = UIS:IsKeyDown(Enum.KeyCode.LeftControl) or UIS:IsKeyDown(Enum.KeyCode.RightControl) if k == Enum.KeyCode.V then toggleEnabled() elseif k == Enum.KeyCode.T then S.autoReturn = not S.autoReturn notify(S.autoReturn and "Retorno automático" or "Mantener posición") elseif k == Enum.KeyCode.L then S.headLook = not S.headLook notify(S.headLook and "Cabeza sigue el cursor" or "Cabeza sigue la cámara") elseif k == Enum.KeyCode.X then S.longReach = not S.longReach notify(S.longReach and "Alcance largo" or "Alcance normal") elseif k == Enum.KeyCode.N then S.noclip = not S.noclip notify(S.noclip and "No-clip jugadores ON" or "No-clip jugadores OFF") elseif k == Enum.KeyCode.H then S.help = not S.help helpPanel.Visible = S.help and S.hud elseif k == Enum.KeyCode.U then S.hud = not S.hud panel.Visible = S.hud helpPanel.Visible = S.hud and S.help elseif k == Enum.KeyCode.Y then resetAll() elseif k == Enum.KeyCode.R and not ctrl then if S.recording then stopRecording() else startRecording() end elseif k == Enum.KeyCode.P then if S.playing then S.playing = false notify("Reproducción detenida") else startPlayback() end elseif k == Enum.KeyCode.LeftBracket then CFG.smooth = clamp(CFG.smooth - 2, 6, 60) elseif k == Enum.KeyCode.RightBracket then CFG.smooth = clamp(CFG.smooth + 2, 6, 60) elseif k == Enum.KeyCode.One or k == Enum.KeyCode.Two or k == Enum.KeyCode.Three then local n = k == Enum.KeyCode.One and 1 or (k == Enum.KeyCode.Two and 2 or 3) if ctrl then savePose(n) else loadPose(n) end end end) CAS:BindActionAtPriority("NexusVRPC_Wheel", function(_, inputState, input) if not S.enabled or not currentMode then return Enum.ContextActionResult.Pass end if inputState ~= Enum.UserInputState.Change then return Enum.ContextActionResult.Sink end local dz = input.Position.Z if UIS:IsKeyDown(Enum.KeyCode.LeftShift) and currentMode == "both" then CFG.gripWidth = clamp(CFG.gripWidth + dz * 0.1, 0.2, 4) else local d = dz * 0.2 local function bump(h) h.depth = clamp(h.depth + d, CFG.depthMin, CFG.depthMax) end if currentMode == "L" then bump(hands.L) elseif currentMode == "R" then bump(hands.R) else bump(hands.L) bump(hands.R) end end return Enum.ContextActionResult.Sink end, false, Enum.ContextActionPriority.High.Value, Enum.UserInputType.MouseWheel) local lastSend = 0 local statusAcc = 0 bind(RunService.RenderStepped, function(dt) dt = math.min(dt, 0.1) cam = workspace.CurrentCamera statusAcc = statusAcc + dt if statusAcc >= 0.1 then statusAcc = 0 refreshStatus() end local char = plr.Character local root = char and char:FindFirstChild("HumanoidRootPart") if not root or not S.enabled then state.valid = false markerL.Visible = false markerR.Visible = false return end local now = os.clock() local rootCF = root.CFrame local chest = rootCF.Position + rootCF.UpVector * 0.6 local loc = UIS:GetMouseLocation() local ray = cam:ViewportPointToRay(loc.X, loc.Y) local function pointAt(depth) local s = (chest - ray.Origin):Dot(ray.Direction) + depth return rootCF:PointToObjectSpace(ray.Origin + ray.Direction * s) end local mode = getMode() currentMode = mode local activeL = mode == "L" or mode == "both" or mode == "mirror" local activeR = mode == "R" or mode == "both" or mode == "mirror" if mode == "L" then hands.L.target = clampReach(hands.L, pointAt(hands.L.depth)) elseif mode == "R" then hands.R.target = clampReach(hands.R, pointAt(hands.R.depth)) elseif mode == "both" then local p = pointAt((hands.L.depth + hands.R.depth) / 2) local rv = rootCF:VectorToObjectSpace(cam.CFrame.RightVector) hands.L.target = clampReach(hands.L, p - rv * (CFG.gripWidth / 2)) hands.R.target = clampReach(hands.R, p + rv * (CFG.gripWidth / 2)) elseif mode == "mirror" then local p = pointAt(hands.R.depth) local x = math.max(abs(p.X), 0.3) hands.R.target = clampReach(hands.R, V3(x, p.Y, p.Z)) hands.L.target = clampReach(hands.L, V3(-x, p.Y, p.Z)) end local rd = (key(Enum.KeyCode.Z) and -1 or 0) + (key(Enum.KeyCode.C) and 1 or 0) for k, h in pairs(hands) do local active = (k == "L" and activeL) or (k == "R" and activeR) if active then h.pinned = false if rd ~= 0 then h.rollTarget = h.rollTarget + rd * CFG.rollSpeed * dt end elseif S.autoReturn and not h.pinned and not S.playing then h.target = h.rest h.rollTarget = 0 end end if S.recording then rec.t = rec.t + dt rec.frames[#rec.frames + 1] = { t = rec.t, L = {pos = hands.L.target, roll = hands.L.rollTarget}, R = {pos = hands.R.target, roll = hands.R.rollTarget}, } if rec.t >= CFG.maxRecord then stopRecording() end end if S.playing then play.t = play.t + dt local total = rec.frames[#rec.frames].t if play.t > total then play.t = play.t - total play.i = 1 end local a, b, al = sampleRecording(play.t) for k, h in pairs(hands) do local active = (k == "L" and activeL) or (k == "R" and activeR) if not active then h.target = a[k].pos:Lerp(b[k].pos, al) h.rollTarget = a[k].roll + (b[k].roll - a[k].roll) * al h.pinned = true end end end local phase = 0 for _, h in pairs(hands) do local sway = V3(0, sin(now * 1.6 + phase) * 0.012, cos(now * 1.3 + phase) * 0.008) stepSpring(h, h.target + sway, dt) buildHandCF(h) phase = phase + 1.5 end local headPos = rootCF.Position + V3(0, 2, 0) local headTarget if S.headLook then headTarget = CFrame.lookAt(headPos, headPos + ray.Direction) else headTarget = CFrame.new(headPos) * cam.CFrame.Rotation end if S.headLook and headSmooth then headSmooth = headSmooth:Lerp(headTarget, 1 - exp(-dt * 18)) else headSmooth = headTarget end state.valid = true state.rootCF = rootCF state.headCF = headSmooth state.dirL = rootCF:VectorToWorldSpace(hands.L.dir) state.dirR = rootCF:VectorToWorldSpace(hands.R.dir) applyJoints() if remote and now - lastSend >= CFG.sendRate then lastSend = now remote:FireServer(headSmooth, rootCF * hands.L.cf, rootCF * hands.R.cf) end if S.hud then local pl, onL = cam:WorldToViewportPoint(rootCF:PointToWorldSpace(hands.L.pos)) local pr, onR = cam:WorldToViewportPoint(rootCF:PointToWorldSpace(hands.R.pos)) markerL.Visible = onL markerR.Visible = onR markerL.Position = UDim2.fromOffset(pl.X, pl.Y) markerR.Position = UDim2.fromOffset(pr.X, pr.Y) markerL.BackgroundTransparency = activeL and 0.2 or 0.7 markerR.BackgroundTransparency = activeR and 0.2 or 0.7 else markerL.Visible = false markerR.Visible = false end end) local ncAcc = 0 bind(RunService.Stepped, function(_, dt) applyJoints() if S.noclip then ncAcc = ncAcc + dt if ncAcc >= 0.1 then ncAcc = 0 for _, other in ipairs(Players:GetPlayers()) do if other ~= plr and other.Character then for _, p in ipairs(other.Character:GetChildren()) do if p:IsA("BasePart") then p.CanCollide = false end end end end end end end) shared.NexusVRPC_Cleanup = function() for _, c in ipairs(conns) do c:Disconnect() end CAS:UnbindAction("NexusVRPC_Wheel") S.enabled = false resetJoints() if gui then gui:Destroy() end shared.NexusVRPC_Cleanup = nil end refreshStatus() notify("Nexus VR PC listo · H para ayuda")