So I'm making a datastore script, it's located in server script service and it's a regular script. I made the script and it worked fine until I added all of the car bool values. The goal of the script is to save money xp rank and then a bunch of bool value's that are the cars people have bought. once I added the car bools everything stopped saving. I tried some trouble shooting but nothing seems to work. I'm not very experienced in datastores.
Also the event that fires when it loads the player data is suppose to put the car models of the cars that the player has purchased in owned cars and that event script works fine, the only issue is the values ether won't save or load.
Heres the script:
local DataStore = game:GetService("DataStoreService"):GetDataStore("RedLineRacing-DS") local EventTutorial = game:GetService("ReplicatedStorage").TutorialEvent local EventRank = game:GetService("ReplicatedStorage").RankUpLocal local EventCarLoaded = game:GetService("ReplicatedStorage").PlayerLoadedCarsLocal game.Players.PlayerAdded:Connect(function(player) local key = "Player-ID:"..player.userId local CarStorage = Instance.new("Folder", player) CarStorage.Name = "OwnedCars" local leaderstats = Instance.new("Folder",player) leaderstats.Name = "leaderstats" local Money = Instance.new("NumberValue",leaderstats) Money.Name = "Rowg Coins" Money.Value = 15000 local MiDriven = Instance.new("NumberValue",leaderstats) MiDriven.Name = "Miles Driven" MiDriven.Value = 0 local Rank = Instance.new("StringValue",leaderstats) Rank.Name = "Rank" Rank.Value = "Permit" local Ae86Value = Instance.new("BoolValue", leaderstats) Ae86Value.Name = "Ae86" local A180Value = Instance.new("BoolValue", leaderstats) A180Value.Name = "A180" local A1968ChallengerValue = Instance.new("BoolValue", leaderstats) A1968ChallengerValue.Name = "A1968Challenger" local A911Value = Instance.new("BoolValue", leaderstats) A911Value.Name = "A911" local AccordValue = Instance.new("BoolValue", leaderstats) AccordValue.Name = "Accord" local CamaroValue = Instance.new("BoolValue", leaderstats) CamaroValue.Name = "Camaro" local CorvetteValue = Instance.new("BoolValue", leaderstats) CorvetteValue.Name = "Corvette" local HuracanValue = Instance.new("BoolValue", leaderstats) HuracanValue.Name = "Huracan" local LFAValue = Instance.new("BoolValue", leaderstats) LFAValue.Name = "LFA" local LanEvoValue = Instance.new("BoolValue", leaderstats) LanEvoValue.Name = "LanEvo" local MiataValue = Instance.new("BoolValue", leaderstats) MiataValue.Name = "Miata" local ModelSValue = Instance.new("BoolValue", leaderstats) ModelSValue.Name = "ModelS" local SennaValue = Instance.new("BoolValue", leaderstats) SennaValue.Name = "Senna" local SupraValue = Instance.new("BoolValue", leaderstats) SupraValue.Name = "Supra" local GetSave = DataStore:GetAsync(key) if GetSave then Money.Value = GetSave[1] MiDriven.Value = GetSave[2] Rank.Value = GetSave[3] Ae86Value.Value = GetSave[4] A180Value.Value = GetSave[5] A1968ChallengerValue.Value = GetSave[6] A911Value.Value = GetSave[7] AccordValue.Value = GetSave[8] CamaroValue.Value = GetSave[9] CorvetteValue.Value = GetSave[10] HuracanValue.Value = GetSave[11] LFAValue.Value = GetSave[12] LanEvoValue.Value = GetSave[13] MiataValue.Value = GetSave[14] ModelSValue.Value = GetSave[15] SennaValue.Value = GetSave[16] SupraValue.Value = GetSave[17] EventCarLoaded:FireAllClients() print('tried to fire events') print("Data loaded for"..player.Name) else EventTutorial:FireAllClients() local Numbers = {Money.Value, MiDriven.Value, Rank.Value, Ae86Value.Value, A180Value.Value, A1968ChallengerValue.Value, A911Value.Value, AccordValue.Value, CamaroValue.Value, CorvetteValue.Value, HuracanValue.Value, LFAValue.Value, LanEvoValue.Value, MiataValue.Value, ModelSValue.Value, SennaValue.Value, SupraValue.Value} DataStore:SetAsync(key, Numbers) print("Data Saved for"..player.Name) end MiDriven.Changed:Connect(function() EventRank:FireAllClients() end) end) game.Players.PlayerRemoving:Connect(function(player) local key = "Player-ID:"..player.userId local ValuesToSave = {player.leaderstats["Rowg Coins"].Value, player.leaderstats["Miles Driven"].Value, player.leaderstats.Rank.Value, player.leaderstats.Ae86.Value, player.leaderstats.A180.Value, player.leaderstats.A1968Challenger.Value, player.leaderstats.A911.Value, player.leaderstats.Accord.Value, player.leaderstats.Camaro.Value, player.leaderstats.Corvette.Value, player.leaderstats.Huracan.Value, player.leaderstats.LFA.Value, player.leaderstats.LanEvo.Value, player.leaderstats.Miata.Value, player.leaderstats.ModelS.Value, player.leaderstats.Senna.Value, player.leaderstats.Supra.Value} DataStore:SetAsync(key, ValuesToSave) end)
Does anyone know a solution?
The problem revolves around how you save a table to your DataStore in the PlayerRemoving function. You assume that when you load this table and index the key numerically, it sets the value indexed to the desired ValueObject's Value. When you GetAsync
, you get a table returned to you unordered. Since you have mixed ValueObjects, it wouldn't make sense to set a NumberValue's Value to a string.
This is what I'm getting from how you code is set up. An example we see is using lines '165' and '233':
```lua --165: Money.Value = GetSave[1] --GetSave[1] can sometimes be another bool value
--and 233: local ValuesToSave = { player.leaderstats["Rowg Coins"].Value, player.leaderstats["Miles Driven"].Value, player.leaderstats.Rank.Value, } ```
There is a lot of rework we could do to fix this, specifically cutting down on repeated code. We have Tables and for-loops to help us out. Since you're working on DataStores, I'm assuming you have a basic understanding of Dictionaries and the generic-for.
Utilizing and organizing our code into a Dictionary will help us cut down on a lot of our code to increase readability and provide more efficiency. I want to use a Dictionary to move the bool, string, and number ValueObject code that we will then iterate through to use the Instance.new()
function on.
```lua local PlayerDataTemplate = { --["Name"] = {"ValueObject", defaultValue}, ["RowgCoins"] = {"NumberValue", 15000}, ["Miles Driven"] = {"NumberValue", 0}, ["Rank"] = {"StringValue", "Permit"},
--Try making the next part better: --It works either way. ["Ae86"] = {"BoolValue", false}, ["A180"] = {"BoolValue", false}, ["A1968Challenger"] = {"BoolValue", false}, --and so on...
} ```
Even then, above has some repeated code so you can definitely cut it down some more! I'd assemble every name of the cars into a table of strings then iterate through it since you know they're all BoolValues.
That's a task I'll leave to you.
I now need to iterate through this new dictionary I organized to do several things. These include:
It's almost too easy to do to even explain it. Just read through the following and guess how it works:
```lua --In PlayerAdded after making our folders: for name, object in pairs(PlayerDataTemplate) do local newValue = Instance.new(object[1]) newValue.Name = name newValue.Value = object[2]
if object[1] == "NumberValue" or object[1] == "StringValue" then newValue.Parent = leaderstats else newValue.Parent = CarStorage end
end
```
Since we know that all our BoolValues are owned Cars then we parent these to the CarStorage Folder. Notice how I numerically index through each value in the PlayerDataTemplate
dictionary to read it's name and defaultValue.
Up until now, everything we've done is basically a better, organized, and readable version of your original code.
We now extend in reading our current values that our player has to either save to the DataStore or set when we load it.
```lua --Still inside PlayerAdded after what we did before: local GetSave; local success, message = pcall(function() GetSave = RedlinePlayerDS:GetAsync(playerKey) end)
if success then if GetSave then --Iterate through our CarStorage Folder for _, object in ipairs(CarStorage:GetChildren()) do if object:IsA("BoolValue") then print(object.Name, GetSave[object.Name]) object.Value = GetSave[object.Name] end end
--Iterate through our leaderstats Folder for _, object in ipairs(leaderstats:GetChildren()) do if object:IsA("StringValue") or object:IsA("NumberValue") then object.Value = GetSave[object.Name] end end else --No player data maybe? Continue with defaults. end
else --DSS failed. Notify the player here. end ```
For the above, a few new things you might've never seen but should start looking into. I use a pcall
function to wrap our GetAsync
to catch any network failures. If so, we can handle it appropriately. If not, then we've either loaded the player's data or can see if they even have any.
We tie all this together when we write our saving code. This is because we need to set up our table to properly read from it. So this next part should've been done before the loading part:
```lua --In a PlayerRemoving function: local playerKey = "Player-ID:"..player.UserId
local CarStorage = player.OwnedCars local leaderstats = player.leaderstats
local tableToSave = {};
for _, object in ipairs(CarStorage:GetChildren()) do if object:IsA("BoolValue") then tableToSave[object.Name] = object.Value end end
for _, object in ipairs(leaderstats:GetChildren()) do if object:IsA("StringValue") or object:IsA("NumberValue") then tableToSave[object.Name] = object.Value end end
local success, message = pcall(function() RedlinePlayerDS:SetAsync(playerKey, tableToSave) end) ```
If you look closely, it resembles to how we read from our loaded data. All we really did is flip setting the values to a new index which is the name of our ValueObjects. We then finish with another pcall
to handle the SetAsync
.
```lua local DSS = game:GetService("DataStoreService") local RepStorage = game:GetService("ReplicatedStorage")
local RedlinePlayerDS = DSS:GetDataStore("RedLineRacing-DS") --local EventTutorial = RepStorage.TutorialEvent --local EventRank = RepStorage.RankUpLocal --local EventCarLoaded = RepStorage.PlayerLoadedCarsLocal
local PlayerDataTemplate = { --["Name"] = {"ValueObject", defaultValue}, ["RowgCoins"] = {"NumberValue", 15000}, ["Miles Driven"] = {"NumberValue", 0}, ["Rank"] = {"StringValue", "Permit"},
--Even then, all this below is repeated code so you further cut down. --I'd assemble every name of the cars into a table of strings then --iterate through it the same. That's a task I'll leave to you. ["Ae86"] = {"BoolValue", false}, ["A180"] = {"BoolValue", false}, ["A1968Challenger"] = {"BoolValue", false}, ["A911"] = {"BoolValue", false}, ["Accord"] = {"BoolValue", false}, ["Camaro"] = {"BoolValue", false}, ["Corvette"] = {"BoolValue", false}, ["Huracan"] = {"BoolValue", false}, ["LFA"] = {"BoolValue", false}, ["LanEvo"] = {"BoolValue", false}, ["Miata"] = {"BoolValue", false}, ["ModelS"] = {"BoolValue", false}, ["Senna"] = {"BoolValue", false}, ["Supra"] = {"BoolValue", false}
}
game.Players.PlayerAdded:Connect(function(player) local playerKey = "Player-ID:"..player.UserId
--This can stay since it's no big deal: local CarStorage = Instance.new("Folder", player) CarStorage.Name = "OwnedCars" local leaderstats = Instance.new("Folder",player) leaderstats.Name = "leaderstats" --simple generic-for loop to make new value instances: for name, object in pairs(PlayerDataTemplate) do local newValue = Instance.new(object[1]) newValue.Name = name newValue.Value = object[2] --I'm assuming you actually wanted them parented to "CarStorage" if object[1] == "NumberValue" or object[1] == "StringValue" then newValue.Parent = leaderstats else newValue.Parent = CarStorage end end local GetSave; local success, message = pcall(function() GetSave = RedlinePlayerDS:GetAsync(playerKey) end) if success then --DataStore worked and retrieved something. We continue. if GetSave then for _, object in ipairs(CarStorage:GetChildren()) do if object:IsA("BoolValue") then print(object.Name, GetSave[object.Name]) object.Value = GetSave[object.Name] end end for _, object in ipairs(leaderstats:GetChildren()) do if object:IsA("StringValue") or object:IsA("NumberValue") then object.Value = GetSave[object.Name] end end --EventCarLoaded:FireAllClients() --print('tried to fire events') print("Data loaded for "..player.Name) else --This codeblock means it didn't load data from their saved playerKey. --They continue with default data. You don't need to SetAsync here! --This block can actually be removed unless you want to notify the player. end else --DataStoreService might have ran into a problem. --Warn the player their save can't load. --Ask them to rejoin? end --Not sure what this is for. You shouldn't use it to SetAsync: --MiDriven.Changed:Connect(function() --EventRank:FireAllClients() --end)
end)
game.Players.PlayerRemoving:Connect(function(player) local playerKey = "Player-ID:"..player.UserId
local CarStorage = player.OwnedCars local leaderstats = player.leaderstats --the table we write to using our current player values local tableToSave = {}; for _, object in ipairs(CarStorage:GetChildren()) do if object:IsA("BoolValue") then tableToSave[object.Name] = object.Value end end for _, object in ipairs(leaderstats:GetChildren()) do if object:IsA("StringValue") or object:IsA("NumberValue") then tableToSave[object.Name] = object.Value end end local success, message = pcall(function() RedlinePlayerDS:SetAsync(playerKey, tableToSave) end) if success then if message then print(message) end print("saved success for "..player.Name) end
end) ```
Not many things were changed from your original script. The use of pcall
shouldn't be too difficult to wrap your head around. The full script was tested so make sure you have your place actually published and active.
The only thing left was the RemoteEvent usage of FireAllClients
. I believe you're still sorting that out so I'll leave that to you.