You can write your topic however you want, but you need to answer these questions:
-
What do you want to achieve? Keep it simple and clear!
I want the WorldModel to display my saved outfit’s HumanoidDescription.
Also, if possible, I’d like to improve my script so it can save body parts and skin color as well. -
What is the issue? Include screenshots / videos if possible!
The rig should display my outfit’s appearance. -
What solutions have you tried so far? Did you look for solutions on the Developer Hub?
I’ve tried to make a RemoteEvent that sends HumanoidDescription but I don’t know how to make the thing I want.
After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!
LocalScript
local localPlayer = game:GetService("Players").LocalPlayer
local character = localPlayer.Character
local replicatedStorage = game:GetService("ReplicatedStorage")
local addOutfitButton = script.Parent
local outfitSavingFrame = script.Parent.Parent.Parent.Parent.Parent.Confirmation
local scrollingFrameFits = script.Parent.Parent.Parent.Parent.Parent.Container.ScrollingFrame
local outfitTemplate = script.Parent.Parent.Parent.Fit_FitName
local outfitName = outfitTemplate.FitName
local outfitRig = outfitTemplate.ViewportFrame.WorldModel.Outfit
local rigAppearance = game.ReplicatedStorage.HumanoidDescLoader
local outfitSavingFrameNoButton = script.Parent.Parent.Parent.Parent.Parent.Confirmation.No
local outfitSavingFrameYesButton = script.Parent.Parent.Parent.Parent.Parent.Confirmation.Yes
repeat game:GetService("RunService").Heartbeat:Wait() until localPlayer.CharacterAppearanceLoaded -- Local script will yield until the character has FULLY LOADED
local deleteDebounce = false
function clientDataMessenger(saveOrLoad, key)
print("Client data messenger invoked for", saveOrLoad, "with key:", key)
if saveOrLoad == "Save" then
replicatedStorage.Remotes.SaveOutfit:InvokeServer(outfitSavingFrame.TextBox.Text)
elseif saveOrLoad == "Load" then
replicatedStorage.Remotes.LoadOutfit:InvokeServer(key)
end
end
replicatedStorage.Remotes.AddAccessory.OnClientInvoke = function(returnedValue)
print("AddAccessory invoked on client with returned value:", returnedValue)
if typeof(returnedValue) == "string" then
print("Returned value is a string:", returnedValue)
end
end
addOutfitButton.MouseButton1Click:Connect(function()
outfitSavingFrame.Visible = true
print("Clicked Add Outfit button")
end)
outfitSavingFrameNoButton.MouseButton1Click:Connect(function()
outfitSavingFrame.Visible = false
print("Clicked No on outfit saving frame")
end)
outfitSavingFrameYesButton.MouseButton1Click:Connect(function()
outfitSavingFrame.Visible = false
clientDataMessenger("Save")
outfitSavingFrame.TextBox.Text = ""
print("Saved outfit")
end)
replicatedStorage.Remotes.ListSaves.OnClientEvent:Connect(function(passedData)
print("ListSaves received data:", passedData)
local clonedSaveTemplate = outfitTemplate:Clone()
clonedSaveTemplate.Name = passedData
clonedSaveTemplate.FitName.Text = passedData
clonedSaveTemplate.Parent = scrollingFrameFits
clonedSaveTemplate.Click.MouseButton1Click:Connect(function()
clientDataMessenger("Load", passedData)
print("Clicked to load outfit:", passedData)
end)
clonedSaveTemplate.DeleteIcon.MouseButton1Click:Connect(function()
replicatedStorage.Remotes.RemoveOutfit:InvokeServer(passedData)
clonedSaveTemplate:Destroy()
print("Clicked to delete outfit:", passedData)
end)
end)
replicatedStorage.Remotes.RemoveOutfit.OnClientInvoke = function(success, key)
print("RemoveOutfit invoked on client with success:", success, "and key:", key)
if success then
local outfitToRemove = scrollingFrameFits:FindFirstChild(key)
if outfitToRemove then
outfitToRemove:Destroy()
end
else
task.delay(3, function()
print("Failed to remove outfit after delay")
end)
end
end
ServerScript:
local replicatedStorage = game:GetService("ReplicatedStorage")
local playerTable = {} -- this is some really basic serverside remote security. could be better
-- you can also do this with just a static boolean for each player instead of using tick() but waga baga bobo
local cooldownTime = 0.1
local dataStoreService = game:GetService("DataStoreService")
local outfitStore = dataStoreService:GetDataStore("OutfitStore")
local appearanceChangedRemote = game.ReplicatedStorage.AppearanceChanged
local appearanceResetRemote = game.ReplicatedStorage.AppearanceReset
local rigAppearance = game.ReplicatedStorage.HumanoidDescLoader
function getTextObject(message, fromPlayerId) -- another thing from the offical roblox docs
local textObject
local success, errorMessage = pcall(function()
textObject = game:GetService("TextService"):FilterStringAsync(message, fromPlayerId)
end)
if success then
return textObject
elseif errorMessage then
print("Error generating TextFilterResult:", errorMessage)
end
return false
end
function filterOutfitName(outfitName, player)
local textObject = getTextObject(outfitName, player.UserId)
if textObject then
local filteredName = getFilteredMessage(textObject)
return filteredName
end
return false
end
function getFilteredMessage(textObject)
local filteredMessage
local success, errorMessage = pcall(function()
filteredMessage = textObject:GetNonChatStringForBroadcastAsync()
end)
if success then
return filteredMessage
elseif errorMessage then
print("Error filtering message:", errorMessage)
end
return false
end
function insertAccessory(player, id)
local accessoryCount = 0
for i, v in pairs(player.Character:GetChildren()) do
if v:IsA("Accessory") then
accessoryCount += 1
end
end
if id then
local success, value = pcall(game:GetService("InsertService").LoadAsset, game:GetService("InsertService"), id)
if success and value then
if accessoryCount < 20 then
if value:GetChildren()[1]:IsA("Accessory") then
local accessory = value:GetChildren()[1]
accessory:SetAttribute("AssetId", id)
player.Character:FindFirstChildWhichIsA("Humanoid"):AddAccessory(accessory)
value:Destroy()
return accessory
else
value:Destroy()
warn("First child of LoadAsset is not an accessory")
end
else
warn("Maximum accessories reached")
end
else
warn("Failed to load asset")
end
else
warn("Attempted to load asset with nil ID")
end
end
function serializeCharacter(character)
local characterInfo = {}
characterInfo["Accessories"] = {}
for i, v in pairs(character:GetChildren()) do
if v:IsA("Accessory") then
local specialMesh = v.Handle:FindFirstChildWhichIsA("SpecialMesh")
if specialMesh then
local accessoryInfoTable = {}
accessoryInfoTable["AssetId"] = v:GetAttribute("AssetId")
accessoryInfoTable["TextureId"] = specialMesh.TextureId
accessoryInfoTable["Offset"] = {
X = specialMesh.Offset.X,
Y = specialMesh.Offset.Y,
Z = specialMesh.Offset.Z
}
accessoryInfoTable["Scale"] = {
X = specialMesh.Scale.X,
Y = specialMesh.Scale.Y,
Z = specialMesh.Scale.Z
}
accessoryInfoTable["VertexColor"] = {
X = specialMesh.VertexColor.X,
Y = specialMesh.VertexColor.Y,
Z = specialMesh.VertexColor.Z
}
table.insert(characterInfo["Accessories"], accessoryInfoTable)
else
warn("Accessory " .. v.Name .. " does not have a SpecialMesh")
end
elseif v:IsA("Shirt") then
characterInfo["Shirt"] = v.ShirtTemplate
elseif v:IsA("Pants") then
characterInfo["Pants"] = v.PantsTemplate
elseif v:IsA("ShirtGraphic") then
characterInfo["TShirt"] = v.Graphic
elseif v:IsA("Part") and v.Name == "Head" then
local faceDecal = v:FindFirstChildWhichIsA("Decal")
if faceDecal then
characterInfo["Face"] = faceDecal.Texture
else
warn("Head part does not have a Decal")
end
end
end
print(characterInfo)
return characterInfo
end
function getPlayerData(player)
local success, value = pcall(outfitStore.GetAsync, outfitStore, player.UserId)
if success then
if not value then
value = {}
end
else
print("Error getting player data:", value) -- Print the error message
end
return value
end
game.Players.PlayerAdded:Connect(function(player)
print("Player added:", player.Name)
if not playerTable[player.UserId] then
playerTable[player.UserId] = {}
playerTable[player.UserId]["cooldown"] = tick()
playerTable[player.UserId]["modifying_data"] = false
playerTable[player.UserId]["local_data"] = {}
playerTable[player.UserId]["switch"] = false
end
player.CharacterAppearanceLoaded:Connect(function(character)
print("CharacterAppearanceLoaded event triggered for", player.Name)
character.Humanoid:RemoveAccessories()
local accessoriesTable = game:GetService("Players"):GetHumanoidDescriptionFromUserId(player.UserId):GetAccessories(true)
print("Accessories table:", accessoriesTable)
for i, v in accessoriesTable do
insertAccessory(player, v["AssetId"])
end
-- Check and add default clothing if not present
if not player.Character:FindFirstChildWhichIsA("Shirt") then
local shirt = Instance.new("Shirt")
shirt.ShirtTemplate = "rbxassetid://1"
shirt.Parent = character
end
if not player.Character:FindFirstChildWhichIsA("Pants") then
local pants = Instance.new("Pants")
pants.PantsTemplate = "rbxassetid://1"
pants.Parent = character
end
if not player.Character:FindFirstChildWhichIsA("ShirtGraphic") then
local tShirt = Instance.new("ShirtGraphic")
tShirt.Graphic = "rbxassetid://1"
tShirt.Parent = character
end
if not playerTable[player.UserId]["switch"] then
playerTable[player.UserId]["local_data"] = getPlayerData(player)
playerTable[player.UserId]["switch"] = true
end
local data = playerTable[player.UserId]["local_data"]
for i, v in pairs(data) do
replicatedStorage.Remotes.ListSaves:FireClient(player, i)
end
--replicatedStorage.Remotes.SaveOutfit:InvokeClient(player, "Success")
end)
end)
game.Players.PlayerRemoving:Connect(function(player)
if playerTable[player.UserId] then
local success, value = pcall(function()
outfitStore:SetAsync(player.UserId, playerTable[player.UserId]["local_data"])
end)
if success then
print("Player data saved for", player.Name)
else
print("Error saving player data:", value) -- Print the error message
end
playerTable[player.UserId] = nil
end
print("Player removed:", player.Name)
end)
replicatedStorage.Remotes.SaveOutfit.OnServerInvoke = function(player, outfitName)
print("SaveOutfit invoked by", player.Name, "for outfit:", outfitName)
-- Ensure no concurrent modifications
if playerTable[player.UserId]["modifying_data"] then
print("Modifying data conflict for", player.Name)
return
end
-- Set modifying_data flag to true to prevent concurrent modifications
playerTable[player.UserId]["modifying_data"] = true
local filteredOutfitName = filterOutfitName(outfitName, player)
if not filteredOutfitName then
--replicatedStorage.Remotes.SaveOutfit:InvokeClient(player, "Failed to filter outfit name")
playerTable[player.UserId]["modifying_data"] = false
print("Failed to filter outfit name for", player.Name)
return
end
local playerData = playerTable[player.UserId]["local_data"]
for i, v in pairs(playerData) do
if i == filteredOutfitName then
--replicatedStorage.Remotes.SaveOutfit:InvokeClient(player, "Outfit name already taken")
playerTable[player.UserId]["modifying_data"] = false
print("Outfit name already taken for", player.Name)
return
elseif #playerData >= 50 then
--replicatedStorage.Remotes.SaveOutfit:InvokeClient(player, "Maximum of 50 saves")
playerTable[player.UserId]["modifying_data"] = false
print("Maximum outfit saves reached for", player.Name)
return
end
end
local success, value = pcall(function()
playerData[filteredOutfitName] = serializeCharacter(player.Character)
end)
if success then
replicatedStorage.Remotes.ListSaves:FireClient(player, filteredOutfitName)
--replicatedStorage.Remotes.SaveOutfit:InvokeClient(player, "Success")
print("Outfit saved successfully for", player.Name)
-- Reset modifying_data flag after successful save
playerTable[player.UserId]["modifying_data"] = false
else
print("Error while saving outfit:", value) -- Print the error message
--replicatedStorage.Remotes.SaveOutfit:InvokeClient(player, "Failed to save outfit")
print("Failed to save outfit for", player.Name)
-- Reset modifying_data flag on error
playerTable[player.UserId]["modifying_data"] = false
end
end
replicatedStorage.Remotes.LoadOutfit.OnServerInvoke = function(player, outfitName)
print("LoadOutfit invoked by", player.Name, "for outfit:", outfitName)
if tick() - playerTable[player.UserId]["cooldown"] > cooldownTime and not playerTable[player.UserId]["modifying_data"] then
playerTable[player.UserId]["cooldown"] = tick()
playerTable[player.UserId]["modifying_data"] = true
local retreivedData = playerTable[player.UserId]["local_data"][outfitName]
print("Retrieved data:", retreivedData)
player.Character:FindFirstChildWhichIsA("Humanoid"):RemoveAccessories()
-- Check and set accessories
if retreivedData["Accessories"] then
for i, v in ipairs(retreivedData["Accessories"]) do
local accessory = insertAccessory(player, v["AssetId"])
if accessory then
local mesh = accessory.Handle:FindFirstChildWhichIsA("SpecialMesh")
if mesh then
mesh.Offset = Vector3.new(v["Offset"]["X"], v["Offset"]["Y"], v["Offset"]["Z"])
mesh.Scale = Vector3.new(v["Scale"]["X"], v["Scale"]["Y"], v["Scale"]["Z"])
mesh.VertexColor = Vector3.new(v["VertexColor"]["X"], v["VertexColor"]["Y"], v["VertexColor"]["Z"])
mesh.TextureId = v["TextureId"]
end
end
end
end
-- Check and set clothing
if player.Character:FindFirstChild("Shirt") then
player.Character:FindFirstChild("Shirt").ShirtTemplate = retreivedData["Shirt"]
end
if player.Character:FindFirstChild("Pants") then
player.Character:FindFirstChild("Pants").PantsTemplate = retreivedData["Pants"]
end
if player.Character:FindFirstChild("ShirtGraphic") then
player.Character:FindFirstChild("ShirtGraphic").Graphic = retreivedData["TShirt"]
end
if player.Character.Head:FindFirstChild("Decal") then
player.Character.Head:FindFirstChild("Decal").Texture = retreivedData["Face"]
end
--replicatedStorage.Remotes.LoadOutfit:InvokeClient(player, "Success")
playerTable[player.UserId]["modifying_data"] = false
else
print("Cooldown or modifying data conflict for", player.Name)
end
end
replicatedStorage.Remotes.RemoveOutfit.OnServerInvoke = function(player, outfitName)
print("RemoveOutfit invoked by", player.Name, "for outfit:", outfitName)
if tick() - playerTable[player.UserId]["cooldown"] > cooldownTime and not playerTable[player.UserId]["modifying_data"] then
playerTable[player.UserId]["cooldown"] = tick()
playerTable[player.UserId]["modifying_data"] = true
local playerData = playerTable[player.UserId]["local_data"]
playerData[outfitName] = nil
--replicatedStorage.Remotes.RemoveOutfit:InvokeClient(player, true, outfitName)
playerTable[player.UserId]["modifying_data"] = false
else
print("Cooldown or modifying data conflict for", player.Name)
end
end
local function refreshCharacter(player)
print("Player refreshed avatar:", player.Name)
if not playerTable[player.UserId] then
playerTable[player.UserId] = {}
playerTable[player.UserId]["cooldown"] = tick()
playerTable[player.UserId]["modifying_data"] = false
playerTable[player.UserId]["local_data"] = {}
playerTable[player.UserId]["switch"] = false
end
local character = player.Character
local humanoid = character:WaitForChild("Humanoid")
print("CharacterAppearanceLoaded event triggered for", player.Name)
character.Humanoid:RemoveAccessories()
local humanoidDescription = humanoid:GetAppliedDescription()
if humanoidDescription then
local accessoriesTable = humanoidDescription:GetAccessories(true)
print("Accessories table:", accessoriesTable)
for i, accessory in ipairs(accessoriesTable) do
insertAccessory(player, accessory.AssetId)
end
end
-- Check and add default clothing if not present
if not player.Character:FindFirstChildWhichIsA("Shirt") then
local shirt = Instance.new("Shirt")
shirt.ShirtTemplate = "rbxassetid://1"
shirt.Parent = character
end
if not player.Character:FindFirstChildWhichIsA("Pants") then
local pants = Instance.new("Pants")
pants.PantsTemplate = "rbxassetid://1"
pants.Parent = character
end
if not player.Character:FindFirstChildWhichIsA("ShirtGraphic") then
local tShirt = Instance.new("ShirtGraphic")
tShirt.Graphic = "rbxassetid://1"
tShirt.Parent = character
end
if not playerTable[player.UserId]["switch"] then
playerTable[player.UserId]["local_data"] = getPlayerData(player)
playerTable[player.UserId]["switch"] = true
end
end
local function resetCharacter(player)
print("Player resetted avatar:", player.Name)
if not playerTable[player.UserId] then
playerTable[player.UserId] = {}
playerTable[player.UserId]["cooldown"] = tick()
playerTable[player.UserId]["modifying_data"] = false
playerTable[player.UserId]["local_data"] = {}
playerTable[player.UserId]["switch"] = false
end
local character = player.Character
local humanoid = character:WaitForChild("Humanoid")
character.Humanoid:RemoveAccessories()
local humanoidDescription = humanoid:GetAppliedDescription()
if humanoidDescription then
local accessoriesTable = humanoidDescription:GetAccessories(true)
print("Accessories table:", accessoriesTable)
end
-- Check and add default clothing if not present
if not player.Character:FindFirstChildWhichIsA("Shirt") then
local shirt = Instance.new("Shirt")
shirt.ShirtTemplate = "rbxassetid://1"
shirt.Parent = character
end
if not player.Character:FindFirstChildWhichIsA("Pants") then
local pants = Instance.new("Pants")
pants.PantsTemplate = "rbxassetid://1"
pants.Parent = character
end
if not player.Character:FindFirstChildWhichIsA("ShirtGraphic") then
local tShirt = Instance.new("ShirtGraphic")
tShirt.Graphic = "rbxassetid://1"
tShirt.Parent = character
end
if not playerTable[player.UserId]["switch"] then
playerTable[player.UserId]["local_data"] = getPlayerData(player)
playerTable[player.UserId]["switch"] = true
end
end
appearanceChangedRemote.OnServerEvent:Connect(function(player)
refreshCharacter(player)
end)
appearanceResetRemote.Event:Connect(function(player)
resetCharacter(player)
end)