Issue with datastores and PlayerRemoving

Hello.
I am trying to make a simple system that will save an array as a datastore.
But I have an issue:
Right now I am using this script:
game.Players.PlayerAdded:connect(function(player)
local CurrentExp = player:WaitForChild(“PlayerGui”):WaitForChild(“EXPBarUP”).CurrentExp
local GetSync = AllDataStores:GetAsync(player.UserId)

	if GetSync ~= nil then
		CurrentExp.Value = GetSync[3]
		DataStoreFunction:FireClient(player, GetSync[1], GetSync[2])
	end
	DataStoreFunction.OnServerEvent:Connect(function(Player, BlueSizeTransfer, MaxExpTransfer, CurrentExpTransfer)
		local TransferValues = {BlueSizeTransfer, MaxExpTransfer, CurrentExpTransfer}
		AllDataStores:SetAsync(player.UserId, TransferValues)
	end)		
end)

It uploads every time the CurrentExp changes, it causes many warnings because the CurrentExp changes every a few seconds, but it works.
I tried making the same with PlayerAdded and PlayerRemoving, which looks like that:
game.Players.PlayerAdded:connect(function(player)
local CurrentExp = player:WaitForChild(“PlayerGui”):WaitForChild(“EXPBarUP”).CurrentExp
local GetSync = AllDataStores:GetAsync(player.UserId)

    	if GetSync ~= nil then
    		CurrentExp.Value = GetSync[3]
    		DataStoreFunction:FireClient(player, GetSync[1], GetSync[2])
    	end	
    end)

    game.Players.PlayerRemoving:Connect(function(player)
    	local TransferValues = {BlueSizeTransfer, MaxExpTransfer, CurrentExpTransfer}
    	AllDataStores:SetAsync(player.UserId, TransferValues)
    end)

But no matter of hard I tried, it literally not saves, and everytime sets all the values to 0.
What may be the issue?

It may be a problem with the PlayerRemoving. Try turning it into an autosave with some time interval.

1 Like

I personally think player removing is bad anyway. My experience is that when a player crashes it doesnt fire, when a players internet goes out it doesn’t fire. so I just use game.Players.ChildRemoved:Connect() that seems to always work for me unless they updated player removing.

I think your issue is different though you probably need bind to close which will run right before a server shuts down.

1 Like

PlayerRemoving isn’t bad. Not only is it the proper way of checking for when a player leaves and not ChildRemoved, but the former fires before the latter. PlayerRemoving fires before the actual Player instance is destroyed and is recommended for use when saving data.

This seems more like an implementation issue, but it’s hard to read because the code is not formatted properly. From what I can see though, the transfer variables are only defined for the RemoteEvent and not for PlayerRemoving, so naturally that function doesn’t know what those values are.

@Araxon Could you supply the code you are working with altogether but formatted correctly? You can format best by using a code block. Above and below your code, add three backticks (`). At the top three backticks, you can also add lua.

```lua
-- Your code here
```

1 Like
game.Players.PlayerAdded:connect(function(player)
local CurrentExp = player:WaitForChild(“PlayerGui”):WaitForChild(“EXPBarUP”).CurrentExp
local GetSync = AllDataStores:GetAsync(player.UserId)
	if GetSync ~= nil then
		CurrentExp.Value = GetSync[3]
		DataStoreFunction:FireClient(player, GetSync[1], GetSync[2])
	end
	DataStoreFunction.OnServerEvent:Connect(function(Player, BlueSizeTransfer, MaxExpTransfer, CurrentExpTransfer)
		local TransferValues = {BlueSizeTransfer, MaxExpTransfer, CurrentExpTransfer}
		AllDataStores:SetAsync(player.UserId, TransferValues)
	end)		
end)
game.Players.PlayerAdded:connect(function(player)
local CurrentExp = player:WaitForChild(“PlayerGui”):WaitForChild(“EXPBarUP”).CurrentExp
local GetSync = AllDataStores:GetAsync(player.UserId)

    	if GetSync ~= nil then
    		CurrentExp.Value = GetSync[3]
    		DataStoreFunction:FireClient(player, GetSync[1], GetSync[2])
    	end	
    end)

    game.Players.PlayerRemoving:Connect(function(player)
    	local TransferValues = {BlueSizeTransfer, MaxExpTransfer, CurrentExpTransfer}
    	AllDataStores:SetAsync(player.UserId, TransferValues)
    end)```


First block works perfectly, the second with Player Removing is not
The PlayerRemoving function is inside the PlayerAdded funtion.

Yeah, that’s… that’s the problem. You haven’t set up your events correctly (why is PlayerRemoving even inside PlayerAdded?) and the variables you’re expecting to be available aren’t there so you don’t have anything to actually be saving.

There’s some fundamental flaws in your code that’ll need to be plucked off, such as removing those connections out of PlayerAdded. Another underlying problem here but every time a new player joins, a new connection spawns up, so you’ve also got excess connections too.

PlayerRemoving and the RemoteEvent connection should be divorced from PlayerAdded and please never modify PlayerGuis from the server. Use a RemoteEvent instead to have the server send the client new information to update the Gui with.

I’m not sure where you’re storing values either of if you’re relying on the client to send those, but that seems enormously insecure as well. You should ideally be keeping that kind of data on the server with the client only requesting either for updates which is checked by the server or for the data itself.

If the events are separate, then I’m not quite sure what’s going on here. The PlayerAdded function was posted twice and I think your code block is malformed still. This is also not the full extent of your code. The only thing I know for sure is: in PlayerRemoving, you’re trying to make a table of variables that do not exist for its scope, so it’s not receiving anything.

3 Likes

Okay, thank you so much for that explanation.
I noticed the many issues my script has, and I tried fixing them, saving and loading all the datastores on the server script only:

local DataStoreService = game:GetService("DataStoreService")
local AllDataStores = DataStoreService:GetDataStore("AllDataStores")
local TransferValues

game.Players.PlayerAdded:Connect(function(Player)
	local CurrentExp = Player:WaitForChild("PlayerGui"):WaitForChild("EXPBarUP").CurrentExp.Value
	local BlueSize = Player:WaitForChild("PlayerGui"):WaitForChild("EXPBarUP").Frame
	local GetSync = AllDataStores:GetAsync(Player.UserId)
	
	if GetSync ~= nil then
		CurrentExp = GetSync[1]
		BlueSize.Size = UDim2.new(GetSync[2], BlueSize.Size.X.Offset, BlueSize.Size.Y.Scale, BlueSize.Size.Y.Offset)
	elseif GetSync == nil then
		
	end
end)

game.Players.PlayerRemoving:Connect(function(Player)
	local CurrentExp = Player:WaitForChild("PlayerGui"):WaitForChild("EXPBarUP"):WaitForChild("CurrentExp").Value
	local BlueSize = Player:WaitForChild("PlayerGui"):WaitForChild("EXPBarUP").Frame.Size.X.Scale
	local TransferValues = {CurrentExp, BlueSize}
	
	AllDataStores:SetAsync(Player.UserId, TransferValues)
end)

And of course, the script is not working.
Am I still missing something here?

Yeah, still. You need to stop modifying PlayerGuis from the server and stop relying on the client to report back values. The server will not see client-side changes to a Gui and it should never be interacting with that either. Any Gui changes should only ever be conveyed through by RemoteEvents. The server should also be holding EXP data and giving that to the client to determine the bar fill sizes; the client should not be responsible for its own EXP counting.

1 Like

@Araxon
I use this personally always:

--// Variables
local ds = game:GetService("DataStoreService"):GetDataStore("DataWhateverHere")

local Stats = {"TheGoods", "Cash"} -- Doesnt matter what order. just add new ones


--// Functions
game.Players.PlayerAdded:Connect(function(plr)
	local UserId = plr.UserId
	
	local leaderstats = Instance.new("Folder", plr)
	leaderstats.Name = "leaderstats"
	
	for i=1, #Stats, 1 do -- This will load in each stat stated in the Table
		local Inst = Instance.new("IntValue", leaderstats)
		Inst.Name = Stats[i]
	end
	
	
	local Data = nil
	
	pcall(function()
		Data = ds:GetAsync(UserId)
	end)
	
	if Data then
		for i,v in pairs(leaderstats:GetChildren()) do
			v.Value = LoadedData[v.Name]
		end
	end
end)

game.Players.PlayerRemoving:Connect(function(plr)
	local UserId = plr.UserId
	
	local leaderstats = plr.leaderstats
	
	pcall(function()
		local SaveData = {}
		
		for i,v in pairs(leaderstats:GetChildren()) do
			SaveData[v.Name] = v.Value
		end
		
		ds:SetAsync(UserId, SaveData) -- Some users also recommend :UpdateAsync() but i'm yet to research that. however this works for me. But still.
	end)
end)