Scripting Helpers is winding down operations and is now read-only. More info→
Ad
Log in to vote
0

How do you properly save a boolean value?

Asked by 5 years ago
Edited 5 years ago

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?

0
I broke my scroll wheel for this. xPolarium 1388 — 5y
0
the spaces.. hellmatic 1523 — 5y
0
Yeah scripting helpers now makes you write questions with stack edit, and I have never used stack edit before lol. Babyseal1015 56 — 5y
0
The actual script is only 135 lines of code. Babyseal1015 56 — 5y

1 answer

Log in to vote
1
Answered by
xPolarium 1388 Moderation Voter
5 years ago
Edited 5 years ago

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.


Dictionaries

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.


Generic-For Loop

I now need to iterate through this new dictionary I organized to do several things. These include:

  • Instance.new() every single one
  • Parent each new instance into the proper folders
    • leaderstats Folder
    • OwnedCars Folder
  • Name each new instance and set default value

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.


Saving/Loading

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.


Full Code:

```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) ```

Conclusion

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.


If I missed anything or you need anything explained let me know.
0
Thank you so much!!! This definitely helped me learn more about datastores. And also I heard that it was better to use Update async rather than SetAsync, but it needs to be rapped in some kind of old value function, is that true? Babyseal1015 56 — 5y
Ad

Answer this question