Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- -- steal anims
- local Players = game:GetService("Players")
- local RunService = game:GetService("RunService")
- local UserInputService = game:GetService("UserInputService")
- local HttpService = game:GetService("HttpService")
- local Workspace = game:GetService("Workspace")
- local StarterGui = game:GetService("StarterGui")
- local Selection = game:GetService("Selection")
- local player = Players.LocalPlayer
- local character = player.Character or player.CharacterAdded:Wait()
- local humanoidRootPart = character:WaitForChild("HumanoidRootPart")
- local CLONE_DISTANCE = 5
- local FOLDER_NAME = "StolenAnims"
- local MAX_CHUNK_SIZE = 190000
- -- Two separate clones running in parallel
- local myCloneR6, myCloneR15 = nil, nil
- -- Rest-pose world CFrames captured when the R15 clone spawns; used by R6->R15 converter
- local r15RestPose = {}
- local r15RestHRPCF = CFrame.identity
- local isRecording, recordingType = false, "Player"
- local recordedFrames, recordedFramesBoth_Player, recordedFramesBoth_Model = {}, {}, {}
- local recordConnection, replayConnection, isReplaying = nil, nil, false
- local playerBodyParts, modelBodyParts = {}, {}
- local dragging, dragInput, dragStart, startPos = false, nil, nil, nil
- local selectedModel, originalMotorStates = nil, {}
- local sourceAnimType = "R6"
- local playerAnimType = "R6"
- local modelAnimType = "R6"
- local recordTimestamps, recordTimestamps_Player, recordTimestamps_Model = {}, {}, {}
- local bodyPartNames = {
- "Head","HumanoidRootPart","Left Arm","Left Leg","Right Arm","Right Leg","Torso",
- "UpperTorso","LowerTorso","LeftUpperArm","LeftLowerArm","LeftHand",
- "RightUpperArm","RightLowerArm","RightHand","LeftUpperLeg","LeftLowerLeg",
- "LeftFoot","RightUpperLeg","RightLowerLeg","RightFoot"
- }
- local r6PartNames = {
- "Head","HumanoidRootPart","Left Arm","Left Leg","Right Arm","Right Leg","Torso"
- }
- local r15PartNames = {
- "HumanoidRootPart","Head",
- "UpperTorso","LowerTorso",
- "LeftUpperArm","LeftLowerArm","LeftHand",
- "RightUpperArm","RightLowerArm","RightHand",
- "LeftUpperLeg","LeftLowerLeg","LeftFoot",
- "RightUpperLeg","RightLowerLeg","RightFoot"
- }
- -- Any body part name (R6 or R15) → the R6 equivalent
- -- Used for accessory attachment resolution on the R6 dummy
- local r15ToR6PartName = {
- ["HumanoidRootPart"]="HumanoidRootPart",["Head"]="Head",
- ["Torso"]="Torso",["Left Arm"]="Left Arm",["Right Arm"]="Right Arm",
- ["Left Leg"]="Left Leg",["Right Leg"]="Right Leg",
- ["UpperTorso"]="Torso",["LowerTorso"]="Torso",
- ["LeftUpperArm"]="Left Arm",["LeftLowerArm"]="Left Arm",["LeftHand"]="Left Arm",
- ["RightUpperArm"]="Right Arm",["RightLowerArm"]="Right Arm",["RightHand"]="Right Arm",
- ["LeftUpperLeg"]="Left Leg",["LeftLowerLeg"]="Left Leg",["LeftFoot"]="Left Leg",
- ["RightUpperLeg"]="Right Leg",["RightLowerLeg"]="Right Leg",["RightFoot"]="Right Leg",
- }
- -- ─────────────────────────────────────────────────────────────────────────────
- -- ROTATION UTILITIES
- -- ─────────────────────────────────────────────────────────────────────────────
- local function getPureRotation(cf)
- return CFrame.fromMatrix(Vector3.zero, cf.RightVector, cf.UpVector, -cf.LookVector)
- end
- local function blendRotations(cframes, weights)
- if #cframes == 0 then return CFrame.identity end
- if #cframes == 1 then return getPureRotation(cframes[1]) end
- local result, accW = getPureRotation(cframes[1]), weights[1]
- for i = 2, #cframes do
- accW = accW + weights[i]
- result = result:Lerp(getPureRotation(cframes[i]), weights[i] / accW)
- end
- return result
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- R15 → R6 CONVERSION
- -- ─────────────────────────────────────────────────────────────────────────────
- local function convertR15ToR6Frame(r15Frame)
- local r6Frame = {}
- local function getCF(n) local e = r15Frame[n]; return e and e.CFrame or nil end
- local hrp = getCF("HumanoidRootPart")
- if hrp then r6Frame["HumanoidRootPart"] = { CFrame = hrp } end
- local ut, lt = getCF("UpperTorso"), getCF("LowerTorso")
- if ut then
- r6Frame["Torso"] = { CFrame = getPureRotation(ut) + (lt and (ut.Position*0.55 + lt.Position*0.45) or ut.Position) }
- elseif lt then r6Frame["Torso"] = { CFrame = lt } end
- local head = getCF("Head")
- if head then r6Frame["Head"] = { CFrame = head } end
- local function limb(uN,lN,eN,r6N,uw,lw)
- local u = getCF(uN); if not u then return end
- local l, e = getCF(lN), getCF(eN)
- local rot = l and blendRotations({u,l},{uw,lw}) or getPureRotation(u)
- local bot = e and e.Position or (l and l.Position or u.Position)
- r6Frame[r6N] = { CFrame = rot + u.Position:Lerp(bot, 0.5) }
- end
- limb("LeftUpperArm","LeftLowerArm","LeftHand","Left Arm",0.65,0.35)
- limb("RightUpperArm","RightLowerArm","RightHand","Right Arm",0.65,0.35)
- limb("LeftUpperLeg","LeftLowerLeg","LeftFoot","Left Leg",0.55,0.45)
- limb("RightUpperLeg","RightLowerLeg","RightFoot","Right Leg",0.55,0.45)
- return r6Frame
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- R6 → R15 CONVERSION (new)
- -- ─────────────────────────────────────────────────────────────────────────────
- --[[
- For each R15 part:
- Position → its rest-pose local offset from the rest HRP, re-applied under
- the animated HRP. This keeps UpperArm / LowerArm / Hand spaced
- along the limb correctly as the arm swings.
- Rotation → the rotation of the corresponding R6 limb relative to the HRP.
- All sub-segments of the same R6 limb share this rotation (R6 has
- no per-sub-segment twist, so this is the most faithful mapping).
- restPose : { partName = worldCFrame } captured at R15 clone spawn.
- restHRPCF : world CFrame of the R15 clone HRP at spawn.
- ]]
- local function convertR6ToR15Frame(r6Frame, restPose, restHRPCF)
- local r15Frame = {}
- local function r6CF(n) local e = r6Frame[n]; return e and e.CFrame or nil end
- local animHRP = r6CF("HumanoidRootPart")
- if not animHRP then return r15Frame end
- local restHRPInv = restHRPCF:Inverse()
- for _, partName in ipairs(r15PartNames) do
- local restWorld = restPose[partName]
- if not restWorld then continue end
- local r6Name = r15ToR6PartName[partName]
- local srcCF = r6CF(r6Name)
- -- Rest-pose position in HRP-local space
- local restLocalPos = (restHRPInv * restWorld).Position
- -- Local rotation from R6 source (relative to animated HRP)
- local localRot
- if srcCF then
- localRot = getPureRotation(animHRP:Inverse() * srcCF)
- else
- -- Fallback: keep rest-pose local orientation
- localRot = getPureRotation(restHRPInv * restWorld)
- end
- -- World CFrame = animated HRP position/orientation * rest-local offset * r6 rotation
- r15Frame[partName] = { CFrame = animHRP * CFrame.new(restLocalPos) * localRot }
- end
- return r15Frame
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- TEMPORAL RESAMPLING
- -- ─────────────────────────────────────────────────────────────────────────────
- local function resampleFrames(frames, partNames, targetFPS, recordedFPS)
- if math.abs(targetFPS - recordedFPS) < 1 then return frames end
- local ratio = recordedFPS / targetFPS
- local count = math.max(1, math.floor(#frames * targetFPS / recordedFPS))
- local result = {}
- for i = 1, count do
- local srcIdx = (i-1)*ratio + 1
- local lo = math.max(1, math.floor(srcIdx))
- local hi = math.min(#frames, lo+1)
- local alpha = srcIdx - lo
- if lo == hi or alpha < 0.001 then
- result[i] = frames[lo]
- else
- local fa, fb, blended = frames[lo], frames[hi], {}
- for _, pn in ipairs(partNames) do
- local a, b = fa[pn], fb[pn]
- if a and b then blended[pn] = { CFrame = a.CFrame:Lerp(b.CFrame, alpha) }
- elseif a then blended[pn] = a end
- end
- result[i] = blended
- end
- end
- return result
- end
- local function prepareForR6(frames, sourceType, targetFPS, recFPS)
- local converted = frames
- if sourceType == "R15" then
- converted = {}
- for i, f in ipairs(frames) do converted[i] = convertR15ToR6Frame(f) end
- end
- return resampleFrames(converted, r6PartNames, targetFPS or 60, recFPS or 60)
- end
- local function prepareForR15(frames, sourceType, targetFPS, recFPS)
- local converted = frames
- if sourceType == "R6" then
- converted = {}
- for i, f in ipairs(frames) do
- converted[i] = convertR6ToR15Frame(f, r15RestPose, r15RestHRPCF)
- end
- end
- return resampleFrames(converted, r15PartNames, targetFPS or 60, recFPS or 60)
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- UTILITIES
- -- ─────────────────────────────────────────────────────────────────────────────
- local function detectModelType(model)
- if not model then return "Unknown" end
- if model:FindFirstChild("UpperTorso", true) then return "R15" end
- if model:FindFirstChild("Torso", true) then return "R6" end
- return "Unknown"
- end
- local function estimateFPS(ts)
- if #ts < 2 then return 60 end
- return (#ts-1) / math.max(ts[#ts] - ts[1], 0.001)
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- FILESYSTEM
- -- ─────────────────────────────────────────────────────────────────────────────
- local HasFileSystem = (getgenv and (writefile or getgenv().writefile)) ~= nil
- local IS_STUDIO = not HasFileSystem
- local FileSystem = {}
- if IS_STUDIO then
- local OutputFolder = Workspace:FindFirstChild("Stolen_Anims_Output")
- if not OutputFolder then
- OutputFolder = Instance.new("Folder")
- OutputFolder.Name = "Stolen_Anims_Output"
- OutputFolder.Parent = Workspace
- end
- FileSystem.Enabled = true
- FileSystem.IsFolder = function() return true end
- FileSystem.MakeFolder = function() end
- FileSystem.List = function()
- local files, seen = {}, {}
- for _, child in ipairs(OutputFolder:GetChildren()) do
- if child:IsA("ModuleScript") then
- local base = child.Name:match("(.+)_Part%d+$") or child.Name
- if not seen[base] then seen[base]=true; table.insert(files, FOLDER_NAME.."/"..base..".json") end
- end
- end
- return files
- end
- FileSystem.Write = function(path, data)
- local filename = path:match("([^/]+)%.json$") or "Unknown"
- for _, child in ipairs(OutputFolder:GetChildren()) do
- if child:IsA("ModuleScript") and (child.Name==filename or child.Name:match("^"..filename.."_Part%d+$")) then child:Destroy() end
- end
- local safe = data:gsub("\\","\\\\"):gsub("]]","\\]\\]")
- if #safe > MAX_CHUNK_SIZE then
- for i = 1, math.ceil(#safe/MAX_CHUNK_SIZE) do
- local chunk = safe:sub((i-1)*MAX_CHUNK_SIZE+1, math.min(i*MAX_CHUNK_SIZE,#safe))
- local m = Instance.new("ModuleScript"); m.Name=filename.."_Part"..i; m.Parent=OutputFolder; m.Source="return [[\n"..chunk.."\n]]"
- end
- else
- local m = Instance.new("ModuleScript"); m.Name=filename; m.Parent=OutputFolder; m.Source="return [[\n"..safe.."\n]]"
- end
- end
- FileSystem.Read = function(path)
- local filename = path:match("([^/]+)%.json$")
- local parts = {}
- for i = 1, 100 do
- local m = OutputFolder:FindFirstChild(filename.."_Part"..i)
- if m then local fn=loadstring(m.Source); if fn then table.insert(parts,fn()) end else break end
- end
- if #parts > 0 then return table.concat(parts) end
- local m = OutputFolder:FindFirstChild(filename)
- if m then local fn=loadstring(m.Source); if fn then return fn() end end
- return "[]"
- end
- else
- FileSystem = {
- Enabled=true,
- Write=writefile or getgenv().writefile, Read=readfile or getgenv().readfile,
- List=listfiles or getgenv().listfiles, IsFolder=isfolder or getgenv().isfolder,
- MakeFolder=makefolder or getgenv().makefolder,
- }
- if not FileSystem.IsFolder(FOLDER_NAME) then FileSystem.MakeFolder(FOLDER_NAME) end
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- MODEL SELECTION
- -- ─────────────────────────────────────────────────────────────────────────────
- local function selectModelLogic(model)
- if not model then return end
- selectedModel = model
- modelAnimType = detectModelType(selectedModel)
- StarterGui:SetCore("SendNotification", { Title="Target Locked ["..modelAnimType.."]"; Text=selectedModel.Name; Duration=3; })
- originalMotorStates = {}
- for _, c in ipairs(selectedModel:GetDescendants()) do
- if c:IsA("Motor6D") then
- originalMotorStates[c] = { Part0=c.Part0, Part1=c.Part1, C0=c.C0, C1=c.C1, Parent=c.Parent }
- end
- end
- modelBodyParts = {}
- for _, c in ipairs(selectedModel:GetDescendants()) do
- if c:IsA("BasePart") then table.insert(modelBodyParts, c) end
- end
- end
- local function checkExplorerSelection()
- local ok, sel = pcall(function() return Selection:Get() end)
- if ok and sel and #sel > 0 then
- local target = sel[1]
- local model = target:IsA("Model") and target or target:FindFirstAncestorOfClass("Model")
- if model and model ~= player.Character then selectModelLogic(model); return true end
- end
- return false
- end
- local function trySelect() return checkExplorerSelection() or selectedModel ~= nil end
- local function restoreMotors(model)
- if not model then return end
- for _, d in pairs(model:GetDescendants()) do
- if d:IsA("Motor6D") and originalMotorStates[d] then
- local s = originalMotorStates[d]
- d.Part0, d.Part1, d.C0, d.C1, d.Parent = s.Part0, s.Part1, s.C0, s.C1, s.Parent
- end
- end
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- ACCESSORY COPYING (shared; resolver picks the right target part per clone type)
- -- ─────────────────────────────────────────────────────────────────────────────
- --[[
- partNameResolver(srcBodyPartName) → target part name on this clone.
- R6 clone : maps through r15ToR6PartName (e.g. "LeftUpperArm" → "Left Arm")
- R15 clone : identity pass-through (e.g. "LeftUpperArm" → "LeftUpperArm")
- ]]
- local function copyAccessoriesToClone(clone, resolver)
- if not clone or not player.Character then return end
- for _, c in ipairs(clone:GetChildren()) do if c:IsA("Accessory") then c:Destroy() end end
- for _, item in ipairs(player.Character:GetChildren()) do
- if not item:IsA("Accessory") then continue end
- local handle = item:FindFirstChild("Handle"); if not handle then continue end
- local srcPartName = nil
- -- Method A: legacy Weld / WeldConstraint inside Handle
- for _, child in ipairs(handle:GetChildren()) do
- if child:IsA("Weld") or child:IsA("WeldConstraint") then
- local bp = (child.Part0 ~= handle and child.Part0) or (child.Part1 ~= handle and child.Part1)
- if bp then srcPartName = bp.Name; break end
- end
- end
- -- Method B: RigidConstraint between Attachments
- if not srcPartName then
- for _, d in ipairs(item:GetDescendants()) do
- if d:IsA("RigidConstraint") then
- for _, att in ipairs({d.Attachment0, d.Attachment1}) do
- if att and att.Parent and att.Parent:IsA("BasePart") and att.Parent ~= handle then
- srcPartName = att.Parent.Name; break
- end
- end
- if srcPartName then break end
- end
- end
- end
- -- Method C: matching Attachment name on character body part
- if not srcPartName then
- for _, att in ipairs(handle:GetChildren()) do
- if att:IsA("Attachment") then
- for _, bp in ipairs(player.Character:GetChildren()) do
- if bp:IsA("BasePart") and bp ~= handle and bp:FindFirstChild(att.Name) then
- srcPartName = bp.Name; break
- end
- end
- if srcPartName then break end
- end
- end
- end
- srcPartName = srcPartName or "Head"
- local targetPart = clone:FindFirstChild(resolver(srcPartName))
- if not targetPart then continue end
- local accClone = item:Clone()
- local cloneHandle = accClone:FindFirstChild("Handle")
- if not cloneHandle then accClone:Destroy(); continue end
- cloneHandle.CanCollide = false
- cloneHandle.Anchored = false
- cloneHandle.Massless = true
- for _, child in ipairs(cloneHandle:GetChildren()) do
- if child:IsA("Weld") or child:IsA("WeldConstraint") or child:IsA("RigidConstraint") then
- child:Destroy()
- end
- end
- local origBP = player.Character:FindFirstChild(srcPartName)
- local bpCF = (origBP and origBP ~= handle) and origBP.CFrame or targetPart.CFrame
- local relOffset = bpCF:Inverse() * handle.CFrame
- local weld = Instance.new("Weld")
- weld.Name, weld.Part0, weld.Part1 = "AccessoryWeld", targetPart, cloneHandle
- weld.C0, weld.C1, weld.Parent = relOffset, CFrame.identity, cloneHandle
- accClone.Parent = clone
- end
- end
- local function r6Resolver(n) return r15ToR6PartName[n] or "Head" end
- local function r15Resolver(n) return n end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- R6 CLONE (hand-built dummy)
- -- ─────────────────────────────────────────────────────────────────────────────
- local function applyAppearance(clone)
- local hum = clone and clone:FindFirstChildOfClass("Humanoid"); if not hum then return end
- local ok, desc = pcall(function() return Players:GetHumanoidDescriptionFromUserId(player.UserId) end)
- if ok and desc then pcall(function() hum:ApplyDescriptionClientServer(desc) end) end
- end
- local function createR6Dummy()
- local d = Instance.new("Model"); d.Name = "R6_Clone"
- local function pt(name, size)
- local p = Instance.new("Part"); p.Name,p.Size=name,size
- p.TopSurface,p.BottomSurface=Enum.SurfaceType.Smooth,Enum.SurfaceType.Smooth
- p.CanCollide=false; p.Parent=d; return p
- end
- local function mo(name,p0,p1,c0,c1)
- local m=Instance.new("Motor6D"); m.Name,m.Part0,m.Part1,m.C0,m.C1,m.Parent=name,p0,p1,c0,c1,p0
- end
- local hrp=pt("HumanoidRootPart",Vector3.new(2,2,1)); hrp.Transparency=1
- local head=pt("Head",Vector3.new(2,1,1)); pt("Torso",Vector3.new(2,2,1))
- local torso=d:FindFirstChild("Torso")
- pt("Left Arm",Vector3.new(1,2,1)); pt("Right Arm",Vector3.new(1,2,1))
- pt("Left Leg",Vector3.new(1,2,1)); pt("Right Leg",Vector3.new(1,2,1))
- local hum=Instance.new("Humanoid"); hum.DisplayDistanceType=Enum.HumanoidDisplayDistanceType.None; hum.Parent=d
- local mesh=Instance.new("SpecialMesh"); mesh.MeshType=Enum.MeshType.Head; mesh.Scale=Vector3.new(1.25,1.25,1.25); mesh.Parent=head
- local lArm=d:FindFirstChild("Left Arm"); local rArm=d:FindFirstChild("Right Arm")
- local lLeg=d:FindFirstChild("Left Leg"); local rLeg=d:FindFirstChild("Right Leg")
- mo("Root Joint",hrp,torso,CFrame.new(0,0,0),CFrame.new(0,0,0))
- mo("Neck",torso,head,CFrame.new(0,1,0),CFrame.new(0,-0.5,0))
- mo("Left Shoulder",torso,lArm,CFrame.new(-1,0.5,0),CFrame.new(0.5,0.5,0))
- mo("Right Shoulder",torso,rArm,CFrame.new(1,0.5,0),CFrame.new(-0.5,0.5,0))
- mo("Left Hip",torso,lLeg,CFrame.new(-0.5,-1,0),CFrame.new(0,1,0))
- mo("Right Hip",torso,rLeg,CFrame.new(0.5,-1,0),CFrame.new(0,1,0))
- d.PrimaryPart = hrp; return d
- end
- local function spawnCloneR6()
- if myCloneR6 then myCloneR6:Destroy() end
- character = player.Character or player.CharacterAdded:Wait()
- humanoidRootPart = character:FindFirstChild("HumanoidRootPart"); if not humanoidRootPart then return end
- myCloneR6 = createR6Dummy()
- local cr = myCloneR6:FindFirstChild("HumanoidRootPart")
- if cr then
- cr.CFrame = humanoidRootPart.CFrame * CFrame.new(0, 0, -CLONE_DISTANCE)
- cr.CFrame = CFrame.lookAt(cr.Position, humanoidRootPart.Position)
- end
- myCloneR6.Parent = workspace
- task.wait(0.1)
- applyAppearance(myCloneR6)
- copyAccessoriesToClone(myCloneR6, r6Resolver)
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- R15 CLONE (Moon Animator style — clone the actual character)
- -- ─────────────────────────────────────────────────────────────────────────────
- --[[
- Clones the live character, removes scripts, anchors every part (so no ragdoll),
- and captures rest-pose world CFrames for every R15 body part.
- Placed to the side of the player so both clones are visible simultaneously.
- ]]
- local function spawnCloneR15()
- if myCloneR15 then myCloneR15:Destroy() end
- character = player.Character or player.CharacterAdded:Wait()
- humanoidRootPart = character:FindFirstChild("HumanoidRootPart"); if not humanoidRootPart then return end
- character.Archivable = true
- local clone = character:Clone()
- clone.Name = "R15_Clone"
- -- Strip all scripts (prevents the Animate script from overriding poses)
- for _, obj in ipairs(clone:GetDescendants()) do
- if obj:IsA("LocalScript") or obj:IsA("Script") or obj:IsA("ModuleScript") then
- obj:Destroy()
- end
- end
- local animate = clone:FindFirstChild("Animate")
- if animate then animate:Destroy() end
- -- Position offset from R6 clone (opposite side of player)
- local cloneRoot = clone:FindFirstChild("HumanoidRootPart")
- if cloneRoot then
- cloneRoot.CFrame = humanoidRootPart.CFrame * CFrame.new(CLONE_DISTANCE, 0, 0)
- cloneRoot.CFrame = CFrame.lookAt(cloneRoot.Position, humanoidRootPart.Position)
- end
- -- Anchor all parts — no gravity, no physics, no ragdoll
- for _, part in ipairs(clone:GetDescendants()) do
- if part:IsA("BasePart") then
- part.Anchored = true
- part.CanCollide = false
- end
- end
- clone.Parent = workspace
- myCloneR15 = clone
- -- Capture rest pose NOW (before any animation is applied)
- r15RestPose = {}
- for _, partName in ipairs(r15PartNames) do
- local p = clone:FindFirstChild(partName)
- if p then r15RestPose[partName] = p.CFrame end
- end
- local hrpPart = clone:FindFirstChild("HumanoidRootPart")
- r15RestHRPCF = hrpPart and hrpPart.CFrame or CFrame.identity
- -- Fix accessory welds: the cloned accessories' Part0 still points at the
- -- original character's body parts. copyAccessoriesToClone replaces them.
- task.wait(0.1)
- copyAccessoriesToClone(myCloneR15, r15Resolver)
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- SERIALIZATION
- -- ─────────────────────────────────────────────────────────────────────────────
- local function serializeFrames(frames, animType)
- local data = { animType=animType, frames={} }
- for i, frame in ipairs(frames) do
- data.frames[i] = {}
- for pn, pd in pairs(frame) do data.frames[i][pn] = { pd.CFrame:components() } end
- end
- return HttpService:JSONEncode(data)
- end
- local function deserializeFrames(json)
- local raw = HttpService:JSONDecode(json)
- local frames, animType = {}, "Unknown"
- if raw.animType and raw.frames then
- animType = raw.animType
- for i, rf in ipairs(raw.frames) do
- frames[i] = {}
- for pn, comps in pairs(rf) do frames[i][pn] = { CFrame=CFrame.new(unpack(comps)) } end
- end
- else
- for i, rf in ipairs(raw.frames or raw) do
- frames[i] = {}
- for pn, comps in pairs(rf) do frames[i][pn] = { CFrame=CFrame.new(unpack(comps)) } end
- end
- end
- return frames, animType
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- UI
- -- ─────────────────────────────────────────────────────────────────────────────
- local function createUI()
- local gui = Instance.new("ScreenGui")
- gui.Name, gui.ResetOnSpawn = "AnimationStealer", false
- gui.Parent = player:WaitForChild("PlayerGui")
- local mf = Instance.new("Frame")
- mf.Size = UDim2.new(0, 310, 0, 490)
- mf.Position = UDim2.new(0.5,-155,0.5,-245)
- mf.BackgroundColor3 = Color3.fromRGB(28,28,28)
- mf.BorderSizePixel = 1; mf.BorderColor3 = Color3.fromRGB(45,45,45)
- mf.Parent = gui
- local tb = Instance.new("Frame")
- tb.Size = UDim2.new(1,0,0,28); tb.BackgroundColor3 = Color3.fromRGB(38,38,38)
- tb.BorderSizePixel = 0; tb.Parent = mf
- local tl = Instance.new("TextLabel")
- tl.Size=UDim2.new(1,-10,1,0); tl.Position=UDim2.new(0,8,0,0)
- tl.BackgroundTransparency=1; tl.Text="Animation Stealer"
- tl.TextColor3=Color3.fromRGB(220,220,220); tl.TextSize=14
- tl.Font=Enum.Font.SourceSans; tl.TextXAlignment=Enum.TextXAlignment.Left
- tl.Parent=tb
- tb.InputBegan:Connect(function(inp)
- if inp.UserInputType==Enum.UserInputType.MouseButton1 or inp.UserInputType==Enum.UserInputType.Touch then
- dragging=true; dragStart=inp.Position; startPos=mf.Position
- inp.Changed:Connect(function() if inp.UserInputState==Enum.UserInputState.End then dragging=false end end)
- end
- end)
- tb.InputChanged:Connect(function(inp)
- if inp.UserInputType==Enum.UserInputType.MouseMovement or inp.UserInputType==Enum.UserInputType.Touch then dragInput=inp end
- end)
- UserInputService.InputChanged:Connect(function(inp)
- if inp==dragInput and dragging then
- local d=inp.Position-dragStart
- mf.Position=UDim2.new(startPos.X.Scale,startPos.X.Offset+d.X,startPos.Y.Scale,startPos.Y.Offset+d.Y)
- end
- end)
- local sl = Instance.new("TextLabel")
- sl.Size=UDim2.new(1,-16,0,22); sl.Position=UDim2.new(0,8,0,36)
- sl.BackgroundColor3=Color3.fromRGB(22,22,22); sl.BorderSizePixel=1; sl.BorderColor3=Color3.fromRGB(40,40,40)
- sl.Text=" idle"; sl.TextColor3=Color3.fromRGB(160,160,160); sl.TextSize=13
- sl.Font=Enum.Font.Code; sl.TextXAlignment=Enum.TextXAlignment.Left
- sl.Parent=mf
- local function btn(text, pos, color)
- local b = Instance.new("TextButton")
- b.Size=UDim2.new(1,-16,0,32); b.Position=pos
- b.BackgroundColor3=color; b.BorderSizePixel=1
- b.BorderColor3=Color3.fromRGB(math.max(0,color.R*255-20),math.max(0,color.G*255-20),math.max(0,color.B*255-20))
- b.Text=text; b.TextColor3=Color3.fromRGB(240,240,240); b.TextSize=13; b.Font=Enum.Font.SourceSansBold
- b.Parent=mf
- b.MouseButton1Down:Connect(function() b.BackgroundColor3=Color3.fromRGB(math.max(0,color.R*255-15),math.max(0,color.G*255-15),math.max(0,color.B*255-15)) end)
- b.MouseButton1Up:Connect(function() b.BackgroundColor3=color end)
- b.MouseLeave:Connect(function() b.BackgroundColor3=color end)
- return b
- end
- -- Half-width button: each takes exactly half the usable width minus a 4px gap
- -- Usable = 310 - 16 = 294; half = (294 - 4) / 2 = 145
- local function halfBtn(text, xOff, yOff, color)
- local b = Instance.new("TextButton")
- b.Size=UDim2.new(0,145,0,32); b.Position=UDim2.new(0,xOff,0,yOff)
- b.BackgroundColor3=color; b.BorderSizePixel=1
- b.BorderColor3=Color3.fromRGB(math.max(0,color.R*255-20),math.max(0,color.G*255-20),math.max(0,color.B*255-20))
- b.Text=text; b.TextColor3=Color3.fromRGB(240,240,240); b.TextSize=13; b.Font=Enum.Font.SourceSansBold
- b.Parent=mf
- b.MouseButton1Down:Connect(function() b.BackgroundColor3=Color3.fromRGB(math.max(0,color.R*255-15),math.max(0,color.G*255-15),math.max(0,color.B*255-15)) end)
- b.MouseButton1Up:Connect(function() b.BackgroundColor3=color end)
- b.MouseLeave:Connect(function() b.BackgroundColor3=color end)
- return b
- end
- local function sep(y)
- local s=Instance.new("Frame"); s.Size=UDim2.new(1,-16,0,1); s.Position=UDim2.new(0,8,0,y)
- s.BackgroundColor3=Color3.fromRGB(50,50,50); s.BorderSizePixel=0; s.Parent=mf
- end
- local bPlayer = btn("Record Player", UDim2.new(0,8,0,68), Color3.fromRGB(55,85,150))
- local bModel = btn("Record Model", UDim2.new(0,8,0,106), Color3.fromRGB(150,55,100))
- local bBoth = btn("Record Both", UDim2.new(0,8,0,144), Color3.fromRGB(120,55,150))
- sep(186)
- local bStop = btn("Stop Recording", UDim2.new(0,8,0,196), Color3.fromRGB(150,50,50))
- local bRefresh = btn("Refresh Selection", UDim2.new(0,8,0,234), Color3.fromRGB(50,110,150))
- sep(276)
- local inName = Instance.new("TextBox")
- inName.Size=UDim2.new(1,-16,0,28); inName.Position=UDim2.new(0,8,0,286)
- inName.Text=""; inName.PlaceholderText="filename"
- inName.BackgroundColor3=Color3.fromRGB(22,22,22); inName.BorderSizePixel=1; inName.BorderColor3=Color3.fromRGB(40,40,40)
- inName.TextColor3=Color3.fromRGB(230,230,230); inName.PlaceholderColor3=Color3.fromRGB(100,100,100)
- inName.TextSize=13; inName.Font=Enum.Font.Code; inName.Parent=mf
- Instance.new("UIPadding",inName).PaddingLeft=UDim.new(0,6)
- local bSave = btn("Save", UDim2.new(0,8,0,320), Color3.fromRGB(50,130,70))
- -- ── Split play row (y=358): left = R6 (purple), right = R15 (teal) ─────
- -- Left : x=8, width=145
- -- Right : x=161 (8 + 145 + 8 gap), width=145 → right edge = 161+145 = 306 ≤ 310-4=306 ✓
- local bPlayR6 = halfBtn("▶ Play R6", 8, 358, Color3.fromRGB(90,60,140))
- local bPlayR15 = halfBtn("▶ Play R15", 157, 358, Color3.fromRGB(40,115,105))
- sep(400)
- local lf = Instance.new("ScrollingFrame")
- lf.Size=UDim2.new(1,-16,0,82); lf.Position=UDim2.new(0,8,0,408)
- lf.BackgroundColor3=Color3.fromRGB(18,18,18); lf.BorderSizePixel=1; lf.BorderColor3=Color3.fromRGB(40,40,40)
- lf.ScrollBarThickness=6; lf.ScrollBarImageColor3=Color3.fromRGB(80,80,80)
- lf.CanvasSize=UDim2.new(0,0,0,0); lf.AutomaticCanvasSize=Enum.AutomaticSize.Y
- lf.Parent=mf
- local ll=Instance.new("UIListLayout"); ll.Padding=UDim.new(0,2); ll.Parent=lf
- local lp=Instance.new("UIPadding"); lp.PaddingTop=UDim.new(0,4); lp.PaddingBottom=UDim.new(0,4)
- lp.PaddingLeft=UDim.new(0,4); lp.PaddingRight=UDim.new(0,4); lp.Parent=lf
- return mf, sl, bPlayer, bModel, bBoth, bStop, bRefresh, bSave, bPlayR6, bPlayR15, inName, lf
- end
- local mainGUI, statusLabel,
- btnPlayer, btnModel, btnBoth,
- btnStop, btnRefresh,
- btnSave, btnPlayR6, btnPlayR15,
- inputName, listFrame = createUI()
- local function updateStatus(text, color)
- statusLabel.Text = " "..text
- if color then statusLabel.TextColor3 = color end
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- RECORDING
- -- ─────────────────────────────────────────────────────────────────────────────
- function startRecording(mode)
- if isRecording then return end
- isRecording = true; recordingType = mode
- recordedFrames, recordedFramesBoth_Player, recordedFramesBoth_Model = {},{},{}
- recordTimestamps, recordTimestamps_Player, recordTimestamps_Model = {},{},{}
- if mode=="Player" then
- playerAnimType = detectModelType(player.Character); sourceAnimType = playerAnimType
- elseif mode=="Model" and selectedModel then
- modelAnimType = detectModelType(selectedModel); sourceAnimType = modelAnimType
- elseif mode=="Both" then
- playerAnimType = detectModelType(player.Character)
- if selectedModel then modelAnimType = detectModelType(selectedModel) end
- sourceAnimType = "R6"
- end
- playerBodyParts = {}
- if player.Character then
- for _, n in ipairs(bodyPartNames) do
- local p = player.Character:FindFirstChild(n)
- if p then table.insert(playerBodyParts, p) end
- end
- end
- modelBodyParts = {}
- if selectedModel then
- for _, c in ipairs(selectedModel:GetDescendants()) do
- if c:IsA("BasePart") then table.insert(modelBodyParts, c) end
- end
- end
- local tag = mode=="Both" and "[P:"..playerAnimType.." M:"..modelAnimType.."]" or "["..sourceAnimType.."]"
- updateStatus("recording "..tag, Color3.fromRGB(200,80,80))
- recordConnection = RunService.RenderStepped:Connect(function()
- local t = os.clock()
- if mode=="Player" or mode=="Both" then
- local f = {}
- for _, p in ipairs(playerBodyParts) do if p and p.Parent then f[p.Name]={CFrame=p.CFrame} end end
- if mode=="Player" then table.insert(recordedFrames,f); table.insert(recordTimestamps,t) end
- if mode=="Both" then table.insert(recordedFramesBoth_Player,f); table.insert(recordTimestamps_Player,t) end
- end
- if (mode=="Model" or mode=="Both") and selectedModel then
- local f = {}
- for _, p in ipairs(modelBodyParts) do if p and p.Parent then f[p.Name]={CFrame=p.CFrame} end end
- if mode=="Model" then table.insert(recordedFrames,f); table.insert(recordTimestamps,t) end
- if mode=="Both" then table.insert(recordedFramesBoth_Model,f); table.insert(recordTimestamps_Model,t) end
- end
- end)
- end
- function stopRecording()
- if not isRecording then return end
- isRecording = false
- if recordConnection then recordConnection:Disconnect() end
- if selectedModel then restoreMotors(selectedModel) end
- updateStatus("stopped", Color3.fromRGB(160,160,160))
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- PLAYBACK — R6 clone
- -- ─────────────────────────────────────────────────────────────────────────────
- function replayOnCloneR6(data, animType, timestamps)
- animType = animType or sourceAnimType
- timestamps = timestamps or recordTimestamps
- if isRecording or isReplaying or not data or #data==0 then return end
- if not myCloneR6 then spawnCloneR6(); task.wait(0.1) end
- isReplaying = true
- local recFPS = estimateFPS(timestamps)
- local framesToPlay = prepareForR6(data, animType, 60, recFPS)
- local tag = animType=="R15" and string.format(" [r15→r6 %.0ffps]",recFPS) or ""
- updateStatus("playing R6"..tag, Color3.fromRGB(80,200,100))
- -- Anchor all R6 parts, refresh accessories, then destroy Motor6Ds
- for _, pn in ipairs(r6PartNames) do
- local p = myCloneR6:FindFirstChild(pn); if p then p.Anchored=true end
- end
- copyAccessoriesToClone(myCloneR6, r6Resolver)
- for _, d in ipairs(myCloneR6:GetDescendants()) do
- if d:IsA("Motor6D") then d:Destroy() end
- end
- local cloneRoot = myCloneR6:FindFirstChild("HumanoidRootPart")
- local firstHRP = framesToPlay[1] and framesToPlay[1]["HumanoidRootPart"]
- local recStart = firstHRP and firstHRP.CFrame or CFrame.identity
- local offsetMat = cloneRoot.CFrame * recStart:Inverse()
- local idx = 1
- replayConnection = RunService.RenderStepped:Connect(function()
- if idx <= #framesToPlay and myCloneR6 and myCloneR6.Parent then
- local fd = framesToPlay[idx]
- for _, pn in ipairs(r6PartNames) do
- local p = myCloneR6:FindFirstChild(pn)
- if p and fd[pn] then p.CFrame = offsetMat * fd[pn].CFrame end
- end
- idx = idx + 1
- else
- if replayConnection then replayConnection:Disconnect() end
- isReplaying = false; spawnCloneR6()
- updateStatus("idle", Color3.fromRGB(160,160,160))
- end
- end)
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- PLAYBACK — R15 clone
- -- ─────────────────────────────────────────────────────────────────────────────
- function replayOnCloneR15(data, animType, timestamps)
- animType = animType or sourceAnimType
- timestamps = timestamps or recordTimestamps
- if isRecording or isReplaying or not data or #data==0 then return end
- -- Spawn/re-spawn to get a fresh rest pose before converting
- if not myCloneR15 then spawnCloneR15(); task.wait(0.2) end
- isReplaying = true
- local recFPS = estimateFPS(timestamps)
- local framesToPlay = prepareForR15(data, animType, 60, recFPS)
- local tag = animType=="R6"
- and string.format(" [r6→r15 %.0ffps]",recFPS)
- or string.format(" [r15 %.0ffps]",recFPS)
- updateStatus("playing R15"..tag, Color3.fromRGB(80,200,160))
- -- Refresh accessories, then destroy Motor6Ds (parts are already anchored)
- copyAccessoriesToClone(myCloneR15, r15Resolver)
- for _, d in ipairs(myCloneR15:GetDescendants()) do
- if d:IsA("Motor6D") then d:Destroy() end
- end
- local cloneRoot = myCloneR15:FindFirstChild("HumanoidRootPart")
- local firstHRP = framesToPlay[1] and framesToPlay[1]["HumanoidRootPart"]
- local recStart = firstHRP and firstHRP.CFrame or CFrame.identity
- local offsetMat = (cloneRoot and cloneRoot.CFrame or CFrame.identity) * recStart:Inverse()
- local idx = 1
- replayConnection = RunService.RenderStepped:Connect(function()
- if idx <= #framesToPlay and myCloneR15 and myCloneR15.Parent then
- local fd = framesToPlay[idx]
- for _, pn in ipairs(r15PartNames) do
- local p = myCloneR15:FindFirstChild(pn)
- if p and fd[pn] then p.CFrame = offsetMat * fd[pn].CFrame end
- end
- idx = idx + 1
- else
- if replayConnection then replayConnection:Disconnect() end
- isReplaying = false; spawnCloneR15()
- updateStatus("idle", Color3.fromRGB(160,160,160))
- end
- end)
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- FILE LIST
- -- ─────────────────────────────────────────────────────────────────────────────
- local function refreshFileList()
- for _, v in pairs(listFrame:GetChildren()) do if v:IsA("TextButton") then v:Destroy() end end
- local files = FileSystem.List(FOLDER_NAME); if not files then return end
- for _, path in ipairs(files) do
- local fileName = path:match("([^/]+)%.json$")
- if fileName then
- local b = Instance.new("TextButton")
- b.Size=UDim2.new(1,-8,0,22); b.BackgroundColor3=Color3.fromRGB(32,32,32); b.BorderSizePixel=0
- b.Text=" "..fileName; b.TextColor3=Color3.fromRGB(200,200,200); b.TextSize=11
- b.Font=Enum.Font.Code; b.TextXAlignment=Enum.TextXAlignment.Left; b.Parent=listFrame
- b.MouseEnter:Connect(function() b.BackgroundColor3=Color3.fromRGB(42,42,42) end)
- b.MouseLeave:Connect(function() b.BackgroundColor3=Color3.fromRGB(32,32,32) end)
- b.MouseButton1Click:Connect(function()
- recordedFrames, sourceAnimType = deserializeFrames(FileSystem.Read(path))
- recordTimestamps = {}
- updateStatus("loaded: "..fileName, Color3.fromRGB(200,200,100))
- end)
- end
- end
- end
- -- ─────────────────────────────────────────────────────────────────────────────
- -- BUTTON WIRING
- -- ─────────────────────────────────────────────────────────────────────────────
- btnRefresh.MouseButton1Click:Connect(function()
- if checkExplorerSelection() then updateStatus("target locked",Color3.fromRGB(80,200,100))
- else updateStatus("select in explorer",Color3.fromRGB(200,140,60)) end
- end)
- btnPlayer.MouseButton1Click:Connect(function() startRecording("Player") end)
- btnModel.MouseButton1Click:Connect(function()
- if not trySelect() then updateStatus("select in explorer",Color3.fromRGB(200,100,60)); return end
- startRecording("Model")
- end)
- btnBoth.MouseButton1Click:Connect(function()
- if not trySelect() then updateStatus("select in explorer",Color3.fromRGB(200,100,60)); return end
- startRecording("Both")
- end)
- btnStop.MouseButton1Click:Connect(stopRecording)
- btnSave.MouseButton1Click:Connect(function()
- if isRecording then stopRecording() end
- local name = inputName.Text ~= "" and inputName.Text or "anim_"..os.time()
- if recordingType=="Player" or recordingType=="Model" then
- if #recordedFrames > 0 then
- FileSystem.Write(FOLDER_NAME.."/"..name..".json", serializeFrames(recordedFrames,sourceAnimType))
- updateStatus("saved: "..name, Color3.fromRGB(80,200,100))
- end
- elseif recordingType=="Both" then
- if #recordedFramesBoth_Player > 0 then FileSystem.Write(FOLDER_NAME.."/"..name.."_player.json", serializeFrames(recordedFramesBoth_Player,playerAnimType)) end
- if #recordedFramesBoth_Model > 0 then FileSystem.Write(FOLDER_NAME.."/"..name.."_model.json", serializeFrames(recordedFramesBoth_Model,modelAnimType)) end
- updateStatus("saved both", Color3.fromRGB(80,200,100))
- end
- refreshFileList()
- end)
- btnPlayR6.MouseButton1Click:Connect(function()
- replayOnCloneR6(recordedFrames, sourceAnimType, recordTimestamps)
- end)
- btnPlayR15.MouseButton1Click:Connect(function()
- replayOnCloneR15(recordedFrames, sourceAnimType, recordTimestamps)
- end)
- UserInputService.InputBegan:Connect(function(input, processed)
- if processed then return end
- if input.KeyCode == Enum.KeyCode.T then spawnCloneR6(); spawnCloneR15() end
- end)
- -- ─────────────────────────────────────────────────────────────────────────────
- -- INIT
- -- ─────────────────────────────────────────────────────────────────────────────
- spawnCloneR6()
- spawnCloneR15()
- refreshFileList()
- StarterGui:SetCore("SendNotification", { Title="Animation Stealer"; Text="Ready (R6 + R15)"; Duration=3; })
Advertisement