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