[[
return function(INITIAL_SNAPSHOT, ENGINE_SOURCE)
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local HttpService = game:GetService("HttpService")
local player = Players.LocalPlayer
local env = getgenv and getgenv() or _G
local key = "LocalAvatarDisguise"
if env.LocalAvatarReplicatedIdle then env.LocalAvatarReplicatedIdle:Stop() end
if env[key] then env[key]:Stop() end
local state = {
connections = {}, avatarConnections = {}, hidden = {}, tracks = {},
version = 0, stopped = false, exportCount = 0
}
env[key] = state
function state:ClearAvatar()
for _, c in ipairs(self.avatarConnections) do c:Disconnect() end
table.clear(self.avatarConnections)
if self.sharedTrack then
pcall(function() self.sharedTrack:Stop(0.15) self.sharedTrack:Destroy() end)
self.sharedTrack = nil
end
if self.sharedAnimation then self.sharedAnimation:Destroy() self.sharedAnimation = nil end
for _, track in pairs(self.tracks) do
pcall(function() track:Stop(0) track:Destroy() end)
end
table.clear(self.tracks)
if self.model then self.model:Destroy() self.model = nil end
for object, transparency in pairs(self.hidden) do
if object.Parent then
pcall(function() object.LocalTransparencyModifier = transparency end)
end
end
table.clear(self.hidden)
end
function state:Clear() self:ClearAvatar() end
function state:Stop()
self.stopped = true
self.version = self.version + 1
self.selectedSnapshot = nil
self:ClearAvatar()
for _, c in ipairs(self.connections) do c:Disconnect() end
if self.gui then self.gui:Destroy() end
if env[key] == self then env[key] = nil end
end
local function connect(signal, callback, avatar)
local c = signal:Connect(callback)
table.insert(avatar and state.avatarConnections or state.connections, c)
return c
end
local function create(class, properties, parent)
local object = Instance.new(class)
for property, value in pairs(properties) do object[property] = value end
object.Parent = parent
return object
end
local function corner(object)
create("UICorner", {CornerRadius = UDim.new(0, 9)}, object)
end
local gui = create("ScreenGui", {
Name = "LocalAvatarChanger", ResetOnSpawn = false, DisplayOrder = 100,
ZIndexBehavior = Enum.ZIndexBehavior.Sibling
}, player:WaitForChild("PlayerGui"))
state.gui = gui
local window = create("Frame", {
Size = UDim2.fromOffset(300, 293), Position = UDim2.new(0.5, -150, 0.12, 0),
BackgroundColor3 = Color3.fromRGB(24, 25, 32), BorderSizePixel = 0, Active = true
}, gui)
corner(window)
create("UIStroke", {Color = Color3.fromRGB(66, 68, 85)}, window)
local title = create("TextLabel", {
Size = UDim2.new(1, -52, 0, 42), Position = UDim2.fromOffset(14, 0),
BackgroundTransparency = 1, Text = "Avatar Changer + Export", Active = true,
Font = Enum.Font.GothamBold, TextSize = 15, TextColor3 = Color3.new(1, 1, 1),
TextXAlignment = Enum.TextXAlignment.Left
}, window)
local close = create("TextButton", {
Size = UDim2.fromOffset(32, 32), Position = UDim2.new(1, -38, 0, 5),
BackgroundTransparency = 1, Text = "×", Font = Enum.Font.GothamBold,
TextSize = 25, TextColor3 = Color3.fromRGB(185, 188, 205)
}, window)
local input = create("TextBox", {
Size = UDim2.new(1, -28, 0, 42), Position = UDim2.fromOffset(14, 49),
BackgroundColor3 = Color3.fromRGB(35, 37, 48), BorderSizePixel = 0,
Text = INITIAL_SNAPSHOT and INITIAL_SNAPSHOT.Username or "2Sh0rt4y0u",
PlaceholderText = "Roblox username", ClearTextOnFocus = false,
Font = Enum.Font.Gotham, TextSize = 15, TextColor3 = Color3.new(1, 1, 1)
}, window)
corner(input)
local function button(text, x, y, width, parent)
local b = create("TextButton", {
Size = UDim2.fromOffset(width or 131, 36), Position = UDim2.fromOffset(x, y),
BackgroundColor3 = text == "Apply" and Color3.fromRGB(111, 83, 230)
or Color3.fromRGB(47, 49, 63), BorderSizePixel = 0,
Text = text, Font = Enum.Font.GothamBold, TextSize = 13,
TextColor3 = Color3.new(1, 1, 1)
}, parent or window)
corner(b)
return b
end
local applyButton = button("Apply", 14, 101)
local restoreButton = button("Restore", 155, 101)
local saveButton = button("Save script", 14, 146)
local copyButton = button("Copy script", 155, 146)
local idsButton = button("View all IDs", 14, 191, 272)
local status = create("TextLabel", {
Size = UDim2.new(1, -28, 0, 52), Position = UDim2.fromOffset(14, 234),
BackgroundTransparency = 1, Text = "Apply an avatar to capture its IDs.",
TextWrapped = true, Font = Enum.Font.Gotham, TextSize = 12,
TextColor3 = Color3.fromRGB(180, 185, 200)
}, window)
local function setStatus(message, failed)
if state.stopped then return end
status.Text = message
status.TextColor3 = failed and Color3.fromRGB(255, 130, 145)
or Color3.fromRGB(180, 185, 200)
end
local dragging, dragStart, startPosition
connect(title.InputBegan, function(event)
if event.UserInputType == Enum.UserInputType.Touch
or event.UserInputType == Enum.UserInputType.MouseButton1 then
dragging, dragStart, startPosition = event, event.Position, window.Position
end
end)
connect(UserInputService.InputChanged, function(event)
if dragging and (event == dragging or (
dragging.UserInputType == Enum.UserInputType.MouseButton1
and event.UserInputType == Enum.UserInputType.MouseMovement)) then
local delta = event.Position - dragStart
window.Position = UDim2.new(startPosition.X.Scale, startPosition.X.Offset + delta.X,
startPosition.Y.Scale, startPosition.Y.Offset + delta.Y)
end
end)
connect(UserInputService.InputEnded, function(event)
if event == dragging then dragging = nil end
end)
local preview = create("Frame", {
Size = UDim2.new(0.9, 0, 0.8, 0), Position = UDim2.new(0.05, 0, 0.1, 0),
BackgroundColor3 = Color3.fromRGB(24, 25, 32), BorderSizePixel = 0,
Visible = false, ZIndex = 10
}, gui)
corner(preview)
local previewTitle = create("TextLabel", {
Size = UDim2.new(1, -110, 0, 40), Position = UDim2.fromOffset(14, 0),
BackgroundTransparency = 1, Text = "Export", Font = Enum.Font.GothamBold,
TextSize = 15, TextColor3 = Color3.new(1, 1, 1),
TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 11
}, preview)
local previewClose = button("Close", 0, 0, 80, preview)
previewClose.Position = UDim2.new(1, -94, 0, 5)
previewClose.ZIndex = 11
local previewScroll = create("ScrollingFrame", {
Size = UDim2.new(1, -28, 1, -62), Position = UDim2.fromOffset(14, 48),
BackgroundColor3 = Color3.fromRGB(15, 17, 23), BorderSizePixel = 0,
ScrollBarThickness = 6, CanvasSize = UDim2.new(),
AutomaticCanvasSize = Enum.AutomaticSize.Y, ZIndex = 11
}, preview)
local previewText = create("TextBox", {
Size = UDim2.new(1, -14, 0, 100), Position = UDim2.fromOffset(7, 5),
AutomaticSize = Enum.AutomaticSize.Y, BackgroundTransparency = 1,
Text = "", TextEditable = true, ClearTextOnFocus = false, MultiLine = true,
TextWrapped = true, TextXAlignment = Enum.TextXAlignment.Left,
TextYAlignment = Enum.TextYAlignment.Top, Font = Enum.Font.Code,
TextSize = 13, TextColor3 = Color3.fromRGB(215, 220, 235), ZIndex = 12
}, previewScroll)
connect(previewClose.Activated, function() preview.Visible = false end)
local function showText(name, text)
previewTitle.Text, previewText.Text = name, text
previewScroll.CanvasPosition = Vector2.zero
preview.Visible = true
end
local properties = {
"BackAccessory", "BodyTypeScale", "ClimbAnimation", "DepthScale", "Face",
"FaceAccessory", "FallAnimation", "FrontAccessory", "GraphicTShirt",
"HairAccessory", "HatAccessory", "Head", "HeadColor", "HeadScale",
"HeightScale", "IdleAnimation", "JumpAnimation", "LeftArm", "LeftArmColor",
"LeftLeg", "LeftLegColor", "MoodAnimation", "NeckAccessory", "Pants",
"ProportionScale", "RightArm", "RightArmColor", "RightLeg", "RightLegColor",
"RunAnimation", "Shirt", "ShouldersAccessory", "StaticFacialAnimation",
"SwimAnimation", "Torso", "TorsoColor", "UseAvatarSettings", "WaistAccessory",
"WalkAnimation", "WidthScale"
}
local childProperties = {
AccessoryDescription = {"AssetId", "AccessoryType", "IsLayered", "Order",
"Position", "Rotation", "Scale", "Puffiness"},
BodyPartDescription = {"AssetId", "BodyPart", "Color", "HeadShape"}
}
local function pack(value)
local kind = typeof(value)
if kind == "Color3" then
return {__type = "Color3", Values = {value.R, value.G, value.B}}
elseif kind == "Vector3" then
return {__type = "Vector3", Values = {value.X, value.Y, value.Z}}
elseif kind == "EnumItem" then
return {__type = "EnumItem", Enum = tostring(value.EnumType):gsub("^Enum%.", ""), Name = value.Name}
elseif kind == "table" then
local result = {}
for k, item in pairs(value) do result[k] = pack(item) end
return result
elseif kind == "string" or kind == "number" or kind == "boolean" then
return value
end
error("Unsupported snapshot value: " .. kind)
end
local function unpackValue(value)
if type(value) ~= "table" then return value end
if value.__type == "Color3" then return Color3.new(value.Values[1], value.Values[2], value.Values[3]) end
if value.__type == "Vector3" then return Vector3.new(value.Values[1], value.Values[2], value.Values[3]) end
if value.__type == "EnumItem" then return Enum[value.Enum][value.Name] end
local result = {}
for k, item in pairs(value) do result[k] = unpackValue(item) end
return result
end
local function capture(description, username, userId, rigType)
local snapshot = {
Version = 1, Username = username, UserId = userId, RigType = rigType.Name,
CapturedAt = os.date("!%Y-%m-%dT%H:%M:%SZ"), Properties = {}, Children = {},
Accessories = pack(description:GetAccessories(true)),
Emotes = pack(description:GetEmotes()),
EquippedEmotes = pack(description:GetEquippedEmotes())
}
for _, property in ipairs(properties) do
local ok, value = pcall(function() return description[property] end)
if ok then snapshot.Properties[property] = pack(value) end
end
for _, child in ipairs(description:GetChildren()) do
local list = childProperties[child.ClassName]
if list then
local record = {ClassName = child.ClassName, Properties = {}}
for _, property in ipairs(list) do
local ok, value = pcall(function() return child[property] end)
if ok then record.Properties[property] = pack(value) end
end
table.insert(snapshot.Children, record)
end
end
return snapshot
end
local function restoreDescription(snapshot)
assert(snapshot.Version == 1, "Unsupported snapshot version.")
local description = Instance.new("HumanoidDescription")
for property, value in pairs(snapshot.Properties) do
local ok, err = pcall(function() description[property] = unpackValue(value) end)
if not ok then warn("Snapshot property " .. property .. ": " .. tostring(err)) end
end
description:SetAccessories(unpackValue(snapshot.Accessories or {}), true)
description:SetEmotes(unpackValue(snapshot.Emotes or {}))
description:SetEquippedEmotes(unpackValue(snapshot.EquippedEmotes or {}))
local used = {}
for _, record in ipairs(snapshot.Children or {}) do
local p = record.Properties
local selected
for _, child in ipairs(description:GetChildren()) do
if not used[child] and child.ClassName == record.ClassName then
if record.ClassName == "AccessoryDescription" then
if child.AssetId == p.AssetId
and child.AccessoryType == unpackValue(p.AccessoryType) then selected = child end
elseif record.ClassName == "BodyPartDescription" then
if child.BodyPart == unpackValue(p.BodyPart) then selected = child end
end
if selected then break end
end
end
if not selected then selected = Instance.new(record.ClassName) end
used[selected] = true
for property, value in pairs(p) do
local ok, err = pcall(function() selected[property] = unpackValue(value) end)
if not ok then warn("Snapshot child " .. property .. ": " .. tostring(err)) end
end
selected.Parent = description
end
return description
end
local defaults = {
R15 = {idle = 507766666, walk = 507777826, run = 507767714, jump = 507765000,
fall = 507767968, climb = 507765644, swim = 507784897, swimidle = 507785072, sit = 2506281703},
R6 = {idle = 180435571, walk = 180426354, run = 180426354, jump = 125750702,
fall = 180436148, climb = 180436334, sit = 178130996}
}
local animationProperties = {idle = "IdleAnimation", walk = "WalkAnimation", run = "RunAnimation",
jump = "JumpAnimation", fall = "FallAnimation", climb = "ClimbAnimation", swim = "SwimAnimation"}
local function owningPart(attachment)
local parent = attachment and attachment.Parent
while parent and not parent:IsA("BasePart") do parent = parent.Parent end
return parent
end
local function attachmentOffset(part, attachment)
if attachment.Parent == part then return attachment.CFrame end
return part.CFrame:ToObjectSpace(attachment.WorldCFrame)
end
local function bottomY(frame, size)
return frame.Position.Y - (math.abs(frame.RightVector.Y) * size.X
+ math.abs(frame.UpVector.Y) * size.Y + math.abs(frame.LookVector.Y) * size.Z) * 0.5
end
local function loadAvatar(target)
if type(target) == "string" then
target = target:match("^%s*(.-)%s*$"):gsub("^@", "")
if target == "" then setStatus("Enter a Roblox username.", true) return end
end
state.version = state.version + 1
local version = state.version
state.selectedSnapshot = nil
state:ClearAvatar()
setStatus(type(target) == "table" and "Loading saved avatar..." or "Loading @" .. target .. "...")
local model, description
local function cancelled() return state.stopped or version ~= state.version end
local ok, err = pcall(function()
local snapshot, rigType
if type(target) == "table" then
snapshot = target
description = restoreDescription(snapshot)
rigType = Enum.HumanoidRigType[snapshot.RigType]
else
local userId = Players:GetUserIdFromNameAsync(target)
description = Players:GetHumanoidDescriptionFromUserIdAsync(userId)
rigType = Enum.HumanoidRigType.R15
local infoOk, info = pcall(function() return Players:GetCharacterAppearanceInfoAsync(userId) end)
if infoOk and info.playerAvatarType == "R6" then rigType = Enum.HumanoidRigType.R6 end
snapshot = capture(description, target, userId, rigType)
end
if cancelled() then return end
local character = player.Character
local humanoid = character and character:FindFirstChildOfClass("Humanoid")
local root = character and character:FindFirstChild("HumanoidRootPart")
assert(humanoid and root, "Your character is not ready.")
model = Players:CreateHumanoidModelFromDescriptionAsync(description, rigType)
if cancelled() or player.Character ~= character then model:Destroy() model = nil return end
local visualHumanoid = model:FindFirstChildOfClass("Humanoid")
local visualRoot = model:FindFirstChild("HumanoidRootPart")
assert(visualHumanoid and visualRoot, "Avatar model is incomplete.")
model.Name = "LocalAvatar_" .. snapshot.Username
visualHumanoid.EvaluateStateMachine = false
visualHumanoid.AutoRotate = false
visualHumanoid.RequiresNeck = false
visualHumanoid.BreakJointsOnDeath = false
visualHumanoid.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None
local parts, bodyParts, feet, graph, headParts, accessories, animationIds = {}, {}, {}, {}, {}, {}, {}
local animate = model:FindFirstChild("Animate")
for name, fallback in pairs(defaults[rigType.Name]) do
local property = animationProperties[name]
local id = property and description[property] or 0
animationIds[name] = "rbxassetid://" .. tostring(id > 0 and id or fallback)
local folder = animate and animate:FindFirstChild(name)
local animation = folder and folder:FindFirstChildWhichIsA("Animation", true)
if animation and animation.AnimationId ~= "" then animationIds[name] = animation.AnimationId end
if snapshot.AnimationIds and snapshot.AnimationIds[name] then animationIds[name] = snapshot.AnimationIds[name] end
end
for _, object in ipairs(model:GetDescendants()) do
if object:IsA("BaseScript") then object:Destroy()
elseif object:IsA("BasePart") then
object.Anchored, object.CanCollide, object.CanTouch, object.CanQuery = true, false, false, false
object.Massless = true
table.insert(parts, object)
if object.Parent == model then
table.insert(bodyParts, object)
graph[object] = {}
if object.Name == "LeftFoot" or object.Name == "RightFoot"
or object.Name == "Left Leg" or object.Name == "Right Leg" then table.insert(feet, object)
elseif object.Name == "Head" then headParts[object] = true end
end
end
end
visualRoot.Transparency = 1
local function addJoint(a, b, c0, c1, joint)
if not graph[a] or not graph[b] then return end
local edge = {a = a, b = b, c0 = c0, c1 = c1, joint = joint}
table.insert(graph[a], edge) table.insert(graph[b], edge)
end
for _, joint in ipairs(model:GetDescendants()) do
if joint:IsA("Motor6D") then addJoint(joint.Part0, joint.Part1, joint.C0, joint.C1, joint)
elseif joint:IsA("AnimationConstraint") then
local a, b = owningPart(joint.Attachment0), owningPart(joint.Attachment1)
if graph[a] and graph[b] then
joint.IsKinematic = true
addJoint(a, b, attachmentOffset(a, joint.Attachment0), attachmentOffset(b, joint.Attachment1), joint)
end
end
end
local function solve(frame, animated)
local poses, queue, index = {[visualRoot] = frame}, {visualRoot}, 1
while index <= #queue do
local part = queue[index]
index = index + 1
for _, edge in ipairs(graph[part]) do
local forward = part == edge.a
local other = forward and edge.b or edge.a
if not poses[other] then
local transform = animated and edge.joint.Transform or CFrame.identity
local relative = edge.c0 * transform * edge.c1:Inverse()
poses[other] = poses[part] * (forward and relative or relative:Inverse())
table.insert(queue, other)
end
end
end
return poses
end
local neutral = solve(CFrame.identity, false)
if rigType == Enum.HumanoidRigType.R15 then
local groups = {}
for _, part in ipairs(bodyParts) do
for _, attachment in ipairs(part:GetChildren()) do
if attachment:IsA("Attachment") and attachment.Name:match("RigAttachment$") then
groups[attachment.Name] = groups[attachment.Name] or {}
table.insert(groups[attachment.Name], attachment)
end
end
end
for _ = 1, #bodyParts do
local repaired = false
for name, group in pairs(groups) do
for _, a0 in ipairs(group) do
if neutral[a0.Parent] then
for _, a1 in ipairs(group) do
if a0.Parent ~= a1.Parent and not neutral[a1.Parent] then
local motor = Instance.new("Motor6D")
motor.Name = name:gsub("RigAttachment$", "")
motor.Part0, motor.Part1, motor.C0, motor.C1 = a0.Parent, a1.Parent, a0.CFrame, a1.CFrame
motor.Parent = a1.Parent
addJoint(motor.Part0, motor.Part1, motor.C0, motor.C1, motor)
repaired = true
neutral = solve(CFrame.identity, false)
end
end
end
end
end
if not repaired then break end
end
end
for _, part in ipairs(bodyParts) do assert(neutral[part], "Missing rig connection: " .. part.Name) end
for _, accessory in ipairs(model:GetChildren()) do
if accessory:IsA("Accessory") then
local handle = accessory:FindFirstChild("Handle")
local targetPart, offset
if handle and handle:IsA("BasePart") then
for _, attachment in ipairs(handle:GetChildren()) do
if attachment:IsA("Attachment") then
for _, body in ipairs(bodyParts) do
local match = body:FindFirstChild(attachment.Name)
if match and match:IsA("Attachment") then
targetPart, offset = body, match.CFrame * attachment.CFrame:Inverse()
break
end
end
end
if targetPart then break end
end
if not targetPart then
local weld = handle:FindFirstChild("AccessoryWeld")
if weld and weld:IsA("Weld") then
if weld.Part0 == handle and graph[weld.Part1] then targetPart, offset = weld.Part1, weld.C1 * weld.C0:Inverse()
elseif weld.Part1 == handle and graph[weld.Part0] then targetPart, offset = weld.Part0, weld.C0 * weld.C1:Inverse() end
end
end
for _, part in ipairs(accessory:GetDescendants()) do
if part:IsA("BasePart") then
if targetPart then
table.insert(accessories, {part = part, target = targetPart,
offset = offset * handle.CFrame:ToObjectSpace(part.CFrame)})
if targetPart.Name == "Head" then headParts[part] = true end
else part.Transparency = 1 end
end
end
end
end
end
local lowest = math.huge
for _, foot in ipairs(feet) do lowest = math.min(lowest, bottomY(neutral[foot], foot.Size)) end
local visualHeight = lowest < math.huge and -lowest or visualHumanoid.HipHeight + visualRoot.Size.Y / 2
local originalHeight = humanoid.HipHeight + root.Size.Y / 2
if humanoid.RigType == Enum.HumanoidRigType.R6 then
local leg = character:FindFirstChild("Left Leg")
originalHeight = originalHeight + (leg and leg.Size.Y or 2)
end
local baseOffset = visualHeight - originalHeight
local rayParams = RaycastParams.new()
rayParams.FilterType = Enum.RaycastFilterType.Exclude
rayParams.FilterDescendantsInstances = {character, model}
rayParams.IgnoreWater, rayParams.RespectCanCollide = true, true
local function draw(grounded)
local look = root.CFrame.LookVector
local direction = Vector3.new(look.X, 0, look.Z)
if direction.Magnitude < 0.001 then direction = Vector3.new(0, 0, -1) end
local position = root.Position + Vector3.new(0, baseOffset, 0)
local poses = solve(CFrame.lookAt(position, position + direction.Unit), true)
local correction = 0
if grounded then
local hit = workspace:Raycast(root.Position + Vector3.new(0, 1, 0),
Vector3.new(0, -(originalHeight + 5), 0), rayParams)
if hit then
local bottom = math.huge
for _, foot in ipairs(feet) do bottom = math.min(bottom, bottomY(poses[foot], foot.Size)) end
if bottom < math.huge then correction = hit.Position.Y + 0.025 - bottom end
end
end
local shift = Vector3.new(0, correction, 0)
for _, part in ipairs(bodyParts) do part.CFrame = poses[part] + shift end
for _, binding in ipairs(accessories) do binding.part.CFrame = poses[binding.target] * binding.offset + shift end
end
draw(false)
model.Parent = workspace
state.model = model
local animator = visualHumanoid:FindFirstChildOfClass("Animator")
if not animator then animator = Instance.new("Animator") animator.Parent = visualHumanoid end
for name, id in pairs(animationIds) do
local animation = Instance.new("Animation")
animation.Name, animation.AnimationId, animation.Parent = name, id, model
local trackOk, track = pcall(function() return animator:LoadAnimation(animation) end)
if trackOk then
track.Looped = name ~= "jump"
track.Priority = name == "idle" and Enum.AnimationPriority.Idle or Enum.AnimationPriority.Movement
state.tracks[name] = track
else warn("Unable to load animation " .. name .. ": " .. tostring(track)) end
end
local sharedStatus = "Shared idle unavailable"
local originalAnimator = humanoid:FindFirstChildOfClass("Animator")
if humanoid.RigType ~= visualHumanoid.RigType then sharedStatus = "Shared idle: R6/R15 mismatch"
elseif originalAnimator and state.tracks.idle then
local animation = Instance.new("Animation")
animation.Name, animation.AnimationId, animation.Parent = "AvatarSharedIdle", animationIds.idle, character
state.sharedAnimation = animation
local sharedOk, track = pcall(function() return originalAnimator:LoadAnimation(animation) end)
if sharedOk then
track.Looped, track.Priority = true, Enum.AnimationPriority.Action
state.sharedTrack, sharedStatus = track, "Shared idle enabled"
else animation:Destroy() state.sharedAnimation = nil warn(tostring(track)) end
end
local currentAnimation
local function pauseShared()
if state.sharedTrack and state.sharedTrack.IsPlaying then state.sharedTrack:Stop(0.15) end
end
local function play(name, speed)
if not state.tracks[name] then name = "idle" end
local track = state.tracks[name]
if not track then return end
if currentAnimation ~= name then
local previous = currentAnimation and state.tracks[currentAnimation]
if previous then previous:Stop(0.15) end
currentAnimation = name
track:Play(0.15)
elseif track.Looped and not track.IsPlaying then track:Play(0.15) end
track:AdjustSpeed(speed or 1)
end
local function hide(object)
if object:IsA("BasePart") or object:IsA("Decal") then
if state.hidden[object] == nil then state.hidden[object] = object.LocalTransparencyModifier end
object.LocalTransparencyModifier = 1
end
end
for _, object in ipairs(character:GetDescendants()) do hide(object) end
connect(character.DescendantAdded, hide, true)
connect(RunService.PreSimulation, function()
for _, part in ipairs(parts) do
if part.Parent then part.Anchored, part.CanCollide, part.CanTouch, part.CanQuery = true, false, false, false end
end
end, true)
play("idle")
connect(RunService.PreRender, function()
if not root.Parent or not model.Parent or player.Character ~= character then pauseShared() return end
local movementState = humanoid:GetState()
local grounded = humanoid.FloorMaterial ~= Enum.Material.Air and not humanoid.Sit
and movementState ~= Enum.HumanoidStateType.Jumping and movementState ~= Enum.HumanoidStateType.Freefall
and movementState ~= Enum.HumanoidStateType.Climbing and movementState ~= Enum.HumanoidStateType.Swimming
draw(grounded)
for object in pairs(state.hidden) do if object.Parent then object.LocalTransparencyModifier = 1 end end
local camera, head = workspace.CurrentCamera, character:FindFirstChild("Head")
local firstPerson = camera and head and (camera.CFrame.Position - head.Position).Magnitude < 1
for part in pairs(headParts) do part.LocalTransparencyModifier = firstPerson and 1 or 0 end
if humanoid.Health <= 0 then
pauseShared()
for _, track in pairs(state.tracks) do if track.IsPlaying then track:Stop(0.15) end end
return
end
local velocity = root.AssemblyLinearVelocity
local speed = Vector3.new(velocity.X, 0, velocity.Z).Magnitude
local moving = humanoid.MoveDirection.Magnitude > 0.05
if humanoid.Sit then play("sit")
elseif movementState == Enum.HumanoidStateType.Jumping then play("jump")
elseif movementState == Enum.HumanoidStateType.Freefall then play("fall")
elseif movementState == Enum.HumanoidStateType.Climbing then play("climb", math.clamp(math.abs(velocity.Y) / 5, 0, 3))
elseif movementState == Enum.HumanoidStateType.Swimming then
if velocity.Magnitude > 1 then play("swim", math.clamp(velocity.Magnitude / 10, 0.2, 3)) else play("swimidle") end
elseif moving and speed > 0.5 then
if speed > 10 then play("run", math.clamp(speed / 16, 0.2, 3)) else play("walk", math.clamp(speed / 8, 0.2, 3)) end
else play("idle") end
local standing = movementState == Enum.HumanoidStateType.Running
or movementState == Enum.HumanoidStateType.RunningNoPhysics or movementState == Enum.HumanoidStateType.Landed
local localIdle = state.tracks.idle
if state.sharedTrack and localIdle and localIdle.IsPlaying and grounded and standing
and not humanoid.PlatformStand and not moving and speed < 0.75 and currentAnimation == "idle" then
local shared = state.sharedTrack
if not shared.IsPlaying then
shared:Play(0.2, 1, 1)
if shared.Length > 0 then shared.TimePosition = localIdle.TimePosition % shared.Length end
end
shared:AdjustSpeed(localIdle.Speed)
else pauseShared() end
end, true)
snapshot.AnimationIds = animationIds
state.selectedSnapshot = snapshot
input.Text = snapshot.Username
setStatus("Applied @" .. snapshot.Username .. " • " .. tostring(#snapshot.Accessories)
.. " accessories captured\n" .. sharedStatus)
end)
if description then description:Destroy() end
if not ok then
if model then model:Destroy() end
if not cancelled() then
state:ClearAvatar()
state.selectedSnapshot = nil
setStatus(tostring(err):match("[^\n]+$") or "Loading failed.", true)
end
warn("Avatar changer: " .. tostring(err))
end
end
local function longQuote(text)
local equals = ""
while (text .. "]" .. equals .. "]"):find("]" .. equals .. "]", 1, true) ~= #text + 1 do
equals = equals .. "="
end
return "[" .. equals .. "[\n" .. text .. "]" .. equals .. "]"
end
local function exportScript()
local snapshot = state.selectedSnapshot
if not snapshot then setStatus("Apply an avatar before exporting.", true) return end
local json = HttpService:JSONEncode(snapshot)
return "local SNAPSHOT_JSON = " .. longQuote(json)
.. "\nlocal SOURCE = " .. longQuote(ENGINE_SOURCE)
.. "\nlocal compile = assert(loadstring, 'This script requires loadstring.')"
.. "\nlocal run = assert(compile(SOURCE))()"
.. "\nrun(game:GetService('HttpService'):JSONDecode(SNAPSHOT_JSON), SOURCE)\n"
end
local function idReport(snapshot)
local lines = {"Username: " .. snapshot.Username, "UserId: " .. tostring(snapshot.UserId),
"Rig: " .. snapshot.RigType, "Captured: " .. snapshot.CapturedAt, "", "BODY / CLOTHING / ANIMATION IDS"}
local idProperties = {"Head", "Torso", "LeftArm", "RightArm", "LeftLeg", "RightLeg", "Face",
"Shirt", "Pants", "GraphicTShirt", "IdleAnimation", "WalkAnimation", "RunAnimation",
"JumpAnimation", "FallAnimation", "ClimbAnimation", "SwimAnimation", "MoodAnimation"}
for _, name in ipairs(idProperties) do table.insert(lines, name .. ": " .. tostring(snapshot.Properties[name] or 0)) end
table.insert(lines, "\nACCESSORIES: " .. tostring(#snapshot.Accessories))
for index, entry in ipairs(unpackValue(snapshot.Accessories)) do
table.insert(lines, tostring(index) .. ". " .. tostring(entry.AccessoryType)
.. " | AssetId: " .. tostring(entry.AssetId) .. " | Layered: " .. tostring(entry.IsLayered)
.. " | Order: " .. tostring(entry.Order or 0))
end
table.insert(lines, "\nRESOLVED PLAYBACK ANIMATIONS")
local names = {}
for name in pairs(snapshot.AnimationIds or {}) do table.insert(names, name) end
table.sort(names)
for _, name in ipairs(names) do table.insert(lines, name .. ": " .. snapshot.AnimationIds[name]) end
table.insert(lines, "\nEMOTES")
local emotes = unpackValue(snapshot.Emotes or {})
local emoteNames = {}
for name in pairs(emotes) do table.insert(emoteNames, name) end
table.sort(emoteNames)
for _, name in ipairs(emoteNames) do
local ids = {}
for _, id in ipairs(emotes[name]) do table.insert(ids, tostring(id)) end
table.insert(lines, name .. ": " .. table.concat(ids, ", "))
end
table.insert(lines, "\nFULL SNAPSHOT JSON\n" .. HttpService:JSONEncode(snapshot))
return table.concat(lines, "\n")
end
connect(applyButton.Activated, function() task.spawn(loadAvatar, input.Text) end)
connect(input.FocusLost, function(enterPressed) if enterPressed then task.spawn(loadAvatar, input.Text) end end)
connect(restoreButton.Activated, function()
state.version = state.version + 1
state.selectedSnapshot = nil
state:ClearAvatar()
setStatus("Original appearance and animations restored.")
end)
connect(close.Activated, function() state:Stop() end)
connect(idsButton.Activated, function()
if not state.selectedSnapshot then setStatus("Apply an avatar first.", true) return end
showText("Avatar IDs", idReport(state.selectedSnapshot))
end)
connect(copyButton.Activated, function()
local scriptText = exportScript()
if not scriptText then return end
local clipboard = setclipboard or toclipboard
if type(clipboard) == "function" then
local ok = pcall(clipboard, scriptText)
if ok then setStatus("Complete saved-avatar script copied.") return end
end
showText("Select and copy this script", scriptText)
setStatus("Clipboard unavailable. Copy from the export window.")
end)
connect(saveButton.Activated, function()
local scriptText = exportScript()
if not scriptText then return end
if type(writefile) ~= "function" then
showText("Copy and save as .lua", scriptText)
setStatus("File saving unavailable. Export opened for copying.")
return
end
state.exportCount = state.exportCount + 1
local s = state.selectedSnapshot
local name = "Avatar_" .. s.Username:gsub("[^%w_]", "_") .. "_" .. tostring(s.UserId)
.. "_" .. tostring(os.time()) .. "_" .. tostring(state.exportCount) .. ".lua"
local path = name
if type(makefolder) == "function" then
local exists = type(isfolder) == "function" and isfolder("avatar_exports")
if exists or pcall(makefolder, "avatar_exports") then path = "avatar_exports/" .. name end
end
local ok, err = pcall(writefile, path, scriptText)
if not ok and path ~= name then path = name ok, err = pcall(writefile, path, scriptText) end
if ok then setStatus("Saved: " .. path)
else showText("Copy and save as .lua", scriptText) setStatus("Save failed. Export opened for copying.", true) warn(tostring(err)) end
end)
connect(player.CharacterAdded, function(character)
local snapshot = state.selectedSnapshot
state.version = state.version + 1
local version = state.version
state:ClearAvatar()
if snapshot then
task.spawn(function()
local humanoid = character:WaitForChild("Humanoid", 15)
local root = character:WaitForChild("HumanoidRootPart", 15)
if humanoid and root and not state.stopped and state.version == version
and player.Character == character then loadAvatar(snapshot) end
end)
end
end)
if INITIAL_SNAPSHOT then
task.spawn(function()
local character = player.Character or player.CharacterAdded:Wait()
local humanoid = character:WaitForChild("Humanoid", 15)
local root = character:WaitForChild("HumanoidRootPart", 15)
if humanoid and root and not state.stopped and state.version == 0 then loadAvatar(INITIAL_SNAPSHOT) end
end)
end
end
]]