Scripting HelpersARCHIVE
A little piece of Roblox history. Preserved for everyone, one question at a time.Read about the archive →
✓ ANSWER ACCEPTED

How do you properly save a boolean value?

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?

4 comments

0

I broke my scroll wheel for this.

0

the spaces..

0

Yeah scripting helpers now makes you write questions with stack edit, and I have never used stack edit before lol.

0

The actual script is only 135 lines of code.

1 answer

✓ Accepted answer

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':

--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.

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:

--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.

--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:

--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:

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.

1 comment

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?

This discussion is preserved as it was. New answers, comments, and votes are closed.