I was working on my character customization system these days and finally I’ve came up with Data Stores, which is a really hard part for me, since I am not that great programmer. Anyways I’ve been using Custom Character (“StarterCharacter”) and placed him in StarterPlayer. Every hair and clothes are custom-made, so they are not ROBLOX assets or anything like that. What am I trying to do? I want to save my character’s clothes and hair using Data Store Service. How am I doing it? I simply made a “Save” button in a ScreenGUI which contains a local script. The following code is in this local script. local Player = game.Players.LocalPlayer
script.Parent.MouseButton1Click:Connect(function()
--Arrays
local Character = {}
for _, v in pairs(Player.Character:GetChildren()) do
if string.sub(v.Name, 1, 4) == "Clot" or string.sub(v.Name, 1, 4) == "Hair" then
if v.ClassName == "Model" then
table.insert(Character, v.Name)
print(table.concat(Character, ' '))
end
end
end
game.ReplicatedStorage.Events.CharacterEvent:FireServer(Character)
print("Event is fired!")
end)
So, I am simply firing an event to the server, that contains everything that character is wearing currently. After that I am “receiving” that event using following DataStore script.
local DataStoreService = game:GetService("DataStoreService")
local PlayerData = DataStoreService:GetDataStore("CharacterData")
local stuff = game.ReplicatedStorage.Accessories
game.ReplicatedStorage.Events.CharacterEvent.OnServerEvent:Connect(function(player, data)
pcall(function()
PlayerData:SetAsync(player.UserId, function(old)
local newdata = data or old
return newdata
end)
end)
end)
game.Players.PlayerAdded:Connect(function(player)
print("1. Player is added!")
player.CharacterAdded:Connect(function(character)
print("2. Character is added!")
local data = PlayerData:GetAsync(player.UserId)
print("3. Data Exists.")
if data then
print("4. Data is here.")
for _,accessory in pairs(stuff:GetChildren()) do
print("5. Finding accessories...")
for _,wearing in pairs(PlayerData:GetAsync(player.UserId)) do
print("6. Finding wearing..")
if wearing == accessory.Name then
print("7. Wearing exists.")
local clone = accessory:Clone()
print("8. Cloning the accessory!")
clone.Parent = character
print("9. Clone is in the character.")
if string.match(clone.Name, "Clot") then
print("10. Welding clothes to character!")
--Welding part
elseif string.match(clone.Name, "Hair") then
print("11. Welding hair to character.")
--Welding part
end
end
end
end
end
end)
end)
And here is the following output when player joins the game.
And here is the output again when player dies in the game.
For some reason my script doesn’t save my character’s accessories ( Clothes, Hair etc.). I made prints to guide me where does code ends. Looks like it can’t find items that character saved. Is there any way I can fix this?
Your LocalScript isn’t the problem, it’s your server script. Specifically this line:
for _,accessory in pairs(stuff:GetChildren()) do
print("5. Finding accessories...")
for _,wearing in pairs(PlayerData:GetAsync(player.UserId)) do --> This here
print("6. Finding wearing..")
I’d assume that the interval of what’s being returned from PlayerData:GetAsync(player.UserId) is nil (have you tried printing that out?) so it’s not looping through anything and therefore has no reason to pass any of the following code through.
You also should NOT be calling GetAsync in a loop like this: call it once and iterate through those results. Repeated calls of GetAsync is bad practice and a good way to unnecessarily waste your request budget. You already have a line that does the GetAsync right below CharacterAdded, local data = PlayerData:GetAsync(player.UserId), so just loop through this.
All in all, I think a rewrite may be necessary. I’m not going to do the rewriting, but there are several issues with your code.
Exploiters can pass whatever they want to the CharacterEvent and save new data. If any of this data is relevant to game play, you also open up the opportunity for exploiters to up and call the remote to save data they shouldn’t be.
Fetching data from a DataStore in CharacterAdded is bad practice. This again has to do with DataStore limitations. You should only poll for data once and then hold session data in a module, with methods to help you update the data. When a save is needed, fetch the session data and save it.
Your object hierarchy may potentially need to be redone, though I haven’t a clue what exactly your object hierarchy looks like.
I’m pretty sure you can, since DataStore keys are coerced into strings. Using a UserId as a key should be fine. Though yeah, I do agree - tostring(Player.UserId) is great for keys.
if string.match(clone.Name, "Clot") then
print("10. Welding clothes to character!")
local LeftUpperArmPart = clone.LeftUpperArmPart
local LowerTorsoPart = clone.LowerTorsoPart
local RightUpperArmPart = clone.RightUpperArmPart
local UpperTorsoPart = clone.UpperTorsoPart
local Weld = Instance.new("Weld", UpperTorsoPart)
Weld.Part0 = UpperTorsoPart.PrimaryPart
Weld.Part1 = game.Players.LocalPlayer.Character:FindFirstChild("UpperTorso")
Weld.C0 = CFrame.new(0, 0, 0) *CFrame.new(0, 0, 0)
local Weld1 = Instance.new("Weld", LowerTorsoPart)
Weld1.Part0 = LowerTorsoPart.PrimaryPart
Weld1.Part1 = game.Players.LocalPlayer.Character:FindFirstChild("LowerTorso")
Weld1.C0 = CFrame.new(0, 0, 0) *CFrame.new(0, 0, 0)
local Weld2 = Instance.new("Weld", LeftUpperArmPart)
Weld2.Part0 = LeftUpperArmPart.PrimaryPart
Weld2.Part1 = game.Players.LocalPlayer.Character:FindFirstChild("LeftUpperArm")
Weld2.C0 = CFrame.new(0, 0, 0) *CFrame.new(0, 0, 0)
local Weld3 = Instance.new("Weld", RightUpperArmPart)
Weld3.Part0 = RightUpperArmPart.PrimaryPart
--Line 40 Weld3.Part1 = game.Players.LocalPlayer.Character:FindFirstChild("RightUpperArm")
Weld3.C0 = CFrame.new(0, 0, 0) *CFrame.new(0, 0, 0)
elseif string.match(clone.Name, "Hair") then
print("11. Welding hair to character.")
local Head = clone
local Weld = Instance.new("Weld", Head)
Weld.Part0 = Head.PrimaryPart
Weld.Part1 = game.Players.LocalPlayer.Character:FindFirstChild("Head")
Weld.C0 = CFrame.new(0, 0, 0) *CFrame.new(0, 0, 0)
elseif data == nil then
print("Data doesn't exist.")
Looks like the code works, but the welding part doesn’t, which I didn’t mention on this post. I received an error in the output that says that LocalPlayer is a nil value in the line 40. However as you can see it passed through all codes that contains the same code as on the line 40. It’s really weird.