Basically, we are kicking the player if the data fails to load. There are two situations where the data returned will be nil: If it fails to load and when the data is just empty (the player is playing for the first time / there’s no previous data). We must deal with the difference between those two situations to avoid dataloss (this is not the only way to avoid it), here is an example:
You might have seen things like this on some bad datastore tutorials:
local DefaultCash = 10
local PlayerCash = CashDatastore:GetAsync(player_id) or DefaultCash
Well, it works. If the player has no cash stored, meaning they’re playing for the first time, :GetAsync method will return nil, and we use the default value in this case. But there’s a huge problem here, what if the data fails to load? Simple, :GetAsync will return nil, and we are going to reset the player’s cash. Imagine someone playing for a long time to get a good amount of cash and for an error like this, their cash is reset.
This is why I’m kicking the player and not saving the data (< this is important) if it fails to load. There are many others solutions for this case, like retry to load the data, rejoin the player with TeleportService, you can also think about a new solution, just use your imagination and don’t forget to test if it actually works.
Well, I have been based on two DataStores that I have seen in the developer forum, and then I tried to combine a little of both. Use the GEILER123456 method to save the data and use BindClose, then use the part of your method to kick the player if there is possible data loss; After all this, I don’t know if my DataStore will work fine
local DataStoreService = game:GetService("DataStoreService")
local configurationsData = DataStoreService:GetDataStore("ConfigurationsData")
Players.PlayerAdded:Connect(function(player)
local DataFolder = Instance.new("Configuration")
DataFolder.Name = "SettingsData"
DataFolder.Parent = player
local GraphicsFolder = Instance.new("Folder")
GraphicsFolder.Name = "Graphics"
GraphicsFolder.Parent = DataFolder
local GameFolder = Instance.new("Folder")
GameFolder.Name = "GameSettings"
GameFolder.Parent = DataFolder
local KeysFolder = Instance.new("Folder")
KeysFolder.Name = "Keys"
KeysFolder.Parent = DataFolder
local success, KeyInfo = pcall(function()
return configurationsData:GetAsync(player.UserId)
end)
if not success then
warn(KeyInfo)
player:Kick("Has sido expulsado del juego para evitar la pérdida de datos, ingrese de nuevo / You have been kicked from the game to avoid data loss, please log in again.")
elseif success then
if KeyInfo ~= nil then
GraphicsFolder:WaitForChild("Shadow").Value = KeyInfo.shadows
shadowSettings[KeyInfo.shadows ](player)
GraphicsFolder:WaitForChild("Water").Value = KeyInfo.water
waterSettings[KeyInfo.water](player)
GraphicsFolder:WaitForChild("Tree").Value = KeyInfo.tree_texture
treeSettings[KeyInfo.tree_texture](player)
GameFolder:WaitForChild("FPS").Value = KeyInfo.fps
ViewFPS[KeyInfo.fps](player)
GameFolder:WaitForChild("Ping").Value = KeyInfo.ping
ViewPing[KeyInfo.ping](player)
GameFolder:WaitForChild("BrilloVol").Value = KeyInfo.gamma
VolumenDeBrillo["Gamma"](player, KeyInfo.gamma)
GameFolder:WaitForChild("PlayerChose").Value = KeyInfo.friends
LumberDrop[KeyInfo.friends](player)
GameFolder:WaitForChild("Language").Value = KeyInfo.language
LanguageConfg[KeyInfo.language](player, "PlayerAdded")
GameFolder:WaitForChild("SlotsInventory").Value = KeyInfo.SizeSlots
SlotsInventory["SlotsSize"](player, KeyInfo.SizeSlots)
KeysFolder:WaitForChild("Crouch").Value = KeyInfo.crouch
CrouchKey[KeyInfo.crouch](player)
else
GraphicsFolder:WaitForChild("Shadow").Value = "Medio"
shadowSettings.Medio(player)
GraphicsFolder:WaitForChild("Water").Value = "Medio"
waterSettings.Medio(player)
GraphicsFolder:WaitForChild("Tree").Value = "Enabled"
treeSettings.Enabled(player)
GameFolder:WaitForChild("FPS").Value = "UnEnabled"
ViewFPS.UnEnabled(player)
GameFolder:WaitForChild("Ping").Value = "UnEnabledPing"
ViewPing.UnEnabledPing(player)
GameFolder:WaitForChild("BrilloVol").Value = 0.5
VolumenDeBrillo["Gamma"](player, 0.5)
GameFolder:WaitForChild("PlayerChose").Value = "ForLocalPlayer"
LumberDrop.ForLocalPlayer(player)
GameFolder:WaitForChild("Language").Value = "English"
LanguageConfg.English(player)
GameFolder:WaitForChild("SlotsInventory").Value = 90
SlotsInventory["SlotsSize"](player, 90)
KeysFolder:WaitForChild("Crouch").Value = "OnePress"
CrouchKey.OnePress(player)
end
end
end)
local function save(player, dontWait)
local SettingsData = player:FindFirstChild("SettingsData")
if SettingsData then
local Graphics = SettingsData:FindFirstChild("Graphics")
local GameSettings = SettingsData:FindFirstChild("GameSettings")
local Keys = SettingsData:FindFirstChild("Keys")
if Graphics and GameSettings and Keys then
local Send_Data = {
shadows = player.SettingsData.Graphics.Shadow.Value;
water = player.SettingsData.Graphics.Water.Value;
tree_texture = player.SettingsData.Graphics.Tree.Value;
fps =player.SettingsData.GameSettings.FPS.Value;
ping = player.SettingsData.GameSettings.Ping.Value;
gamma = player.SettingsData.GameSettings.BrilloVol.Value;
friends = player.SettingsData.GameSettings.PlayerChose.Value;
language = player.SettingsData.GameSettings.Language.Value;
SizeSlots = player.SettingsData.GameSettings.SlotsInventory.Value;
crouch = player.SettingsData.Keys.Crouch.Value;
}
local Success,Error
repeat
Success,Error = pcall(configurationsData.UpdateAsync, configurationsData, player.UserId,function(Data)
return Send_Data
end)
until Success
if Success then
print("Se ha guardado correctamente / Has been saved successfully")
else
print("hubo un error al guardar / there was an error saving")
warn(Error)
end
end
end
end
game:BindToClose(function()
if RunService:IsStudio() then
task.wait(2)
else
local finished = Instance.new("BindableEvent")
local allPlayers = Players:GetPlayers()
local leftPlayers = #allPlayers
for _, player in pairs(allPlayers) do
coroutine.wrap(function()
save(player)
leftPlayers -= 1
if leftPlayers == 0 then
finished:Fire()
end
end)()
end
finished.Event:Wait()
end
end)
Players.PlayerRemoving:Connect(save)
I changed it to fit my values and it gave me a error saying DataStore request was added to queue. If request queue fills, further requests will be dropped. Try sending fewer requests
Hello.
I did this, and it works but seems a bit hardcoded as I must save as like 14 different items.
I tried to use for-loops to do this but it isn’t working.
Code:
-- // Assigning variables //
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("PlaneSkinOwnedSave") -- This can be changed to whatever you want
local function saveData(player) -- The functions that saves data
local skins = player.OwnedSkins
tableToSave = {
}
for i,v in pairs(skins:GetDescendants()) do
if v.Name == "Owned" then
table.insert(tableToSave, v.Value)
print(v.Value)
end
end
local success, err = pcall(function()
dataStore:SetAsync(player.UserId, tableToSave) -- Save the data with the player UserId, and the table we wanna save
end)
if success then -- If the data has been saved
print("Data has been saved!")
else -- Else if the save failed
print("Data hasn't been saved!")
warn(err)
end
end
game.Players.PlayerAdded:Connect(function(player) -- When a player joins the game
wait(1)
-- // Assigning player stats //
local skins = player.OwnedSkins
local toGive = {}
for i,v in pairs(skins:GetDescendants()) do
if v.Name == "Owned" then
table.insert(toGive, v.Value)
end
end
local data -- We will define the data here so we can use it later, this data is the table we saved
local success, err = pcall(function()
data = dataStore:GetAsync(player.UserId) -- Get the data from the datastore
end)
if success and data then -- If there were no errors and player loaded the data
for count = 1,14 do
print(data[count])
toGive[count] = data[count]
end
else -- The player didn't load in the data, and probably is a new player
print("The player has no data!") -- The default will be set to 0
end
end)
game.Players.PlayerRemoving:Connect(function(player) -- When a player leaves
local success, err = pcall(function()
saveData(player) -- Save the data
end)
if success then
print("Data has been saved")
else
print("Data has not been saved!")
end
end)
game:BindToClose(function() -- When the server shuts down
for _, player in pairs(game.Players:GetPlayers()) do -- Loop through all the players
local success, err = pcall(function()
saveData(player) -- Save the data
end)
if success then
print("Data has been saved")
else
print("Data has not been saved!")
end
end
end)
Hi i tried using your datastore tutorial but whenever i leave the game it doesnt save the data. i didnt changed anything in the code. just added the values and another folder.
I also really appreciate the helpful comments and explanation for what each code segment does and why! Very helpful for newer scripters who may have no idea what each segment does.
As an extra side note by the way, in Part 2 for the final code at the top, the second to last ‘end’ still has a parenthesis even though it doesn’t need it.
game:BindToClose(function() – When the server shuts down
for _, player in pairs(game.Players:GetPlayers()) do
saveData(player) – Save the data
end)
end)
well you can do both, you can save everything into 1 string and then get the names by using string.split, or if you want to save it into 1 array then you can loop through each value for the savedWeapons
What a misinformed article lol. While it is obviously bad practice to use while wait() do in cases when a conditional could easily be substituted into the while statement (like the example provided in the doc), there are many cases like the example provided by @Urxpaly45 where it really doesn’t matter and— in fact— increases readability.
Acting as if someone is “a bad programmer” (as the article states) simply for following a certain convention— irrespective of the circumstances surrounding the use case— proves the only bad programmer is the one peddling this misinformation. And exposes their lack of critical thinking skills.
while wait() do is a tool in your coding arsenal. Good programmers know when to use it and when not to. Bad programmers read opinion and accept it as doctrine.
There’s not even a need to do any while wait statements. Just use RunService
RunService.Heartbeat:Connect(function()
-- fires once every frame
task.wait(1) -- add this to make it wait one second every frame before firing code
end)
Would this be good to handle if data wasn’t loaded in? (Ex. There was an error loading data).
I changed the if & elseif statements to check for:
Success and data > load in the data.
Success but no data returned > set the players data to 0 (assuming they are a new player)
Otherwise if no success > Kick the player
local data -- We will define the data here so we can use it later, this data is the table we saved
local success, err = pcall(function()
data = dataStore:GetAsync(player.UserId) -- Get the data from the datastore
end)
if success and data then -- If there were no errors and player loaded the data
Rank.Value = data[1] -- Set the money to the first value of the table (data)
Tokens.Value = data[2] -- Set the coins to the second value of the table (data)
elseif success then -- The player didn't load in the data, and probably is a new player
print("The player has no data!") -- The default will be set to 0
else
warn(err)
player:Kick("Data loading error - check your connection & Roblox status")
end
end)