NumberValue will save in leaderstats, but not as a value inside player when I rejoin [SOLVED]

Hello scripters! I want to save a NumberValue inside the player, but every time I rejoin it doesn’t save and sets the default value as 1 (which is what it is supposed to do if there is no data to load). Moreover, when the NumberValue was a leaderstat is saved it fine, but I want it to be stored inside the player instead, and not as a player’s leaderstat.

A player can increase their Multiplier if they have enough points to buy one. Multipliers increase points gain for players, so they can buy even more multipliers and get more points from it. This is done in a different script of course.

local DataStoreService = game:GetService("DataStoreService")
local playerData = DataStoreService:GetDataStore("PlayerData")

local function onPlayerJoin(player) 
	--other leaderstats go here
	local upgrades = Instance.new("NumberValue") 
	upgrades.Name = "Multiplier"
	upgrades.Parent = player

	local playerUserId = "Player_" .. player.UserId  
	local data = playerData:GetAsync(playerUserId)  

	if data then
		--other leaderstats go here
		--other leaderstats go here
		upgrades.Value = data['Multiplier']
		if upgrades.Value <= 0 then
			upgrades.Value = 1
		end
	else 
		upgrades.Value = 1
	end
end

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.leaderstats:GetChildren()) do
		player_stats[stat.Name] = stat.Value
	end
	return player_stats
end

local function onPlayerExit(player)  
	local player_stats = create_table(player)
	local success, err = pcall(function()
		local playerUserId = "Player_" .. player.UserId
		playerData:SetAsync(playerUserId, player_stats) 
	end)

	if not success then
		warn('Could not save data!')
	end
end

game.Players.PlayerAdded:Connect(onPlayerJoin)
game.Players.PlayerRemoving:Connect(onPlayerExit)

There are other stats in the script, but the one I am showing you is the one that isn’t saving. I used a Datastore Editor Plugin and there was no Multiplier stat saved in the datastore. There is also no warning messages to show that when the player leaves the game, it doesn’t save their Multiplier value. Hopefully I am not missing something obvious?

I tried looking on other topics and videos but nothing really helped or helped me to solved the problem. All help and feedback is appreciated.

4 Likes

Make sure to also save all the data when the server closes.

local PlayerService = game:GetService("Players")

game:BindToClose(function()
   for _, player in pairs(PlayerService:GetPlayers()) do
      coroutine.wrap(onPlayerExit)(player) -- stops yielding
   end
end)

Make sure to use a coroutine here to stop function yielding, because we want to save all the data before the server closes. Also this might save data twice, for this you could use an table on BindToClose or PlayerRemoving

4 Likes

So if you don’t use BindToClose, your more likely to get data loss?

local function onPlayerExit(player)  
	local player_stats = create_table(player)
	local success, err = pcall(function()
		local playerUserId = "Player_" .. player.UserId
		playerData:SetAsync(playerUserId, player_stats) 
	end)

	local PlayerService = game:GetService("Players")

	game:BindToClose(function()
		for _, player in pairs(PlayerService:GetPlayers()) do
			coroutine.wrap(onPlayerExit)(player) -- stops yielding
		end
	end)

	if not success then
		warn('Could not save data!')
	end
end

Would this be under OnPlayerExit? I still don’t really understand why it would save it as a leaderstat, but not as a NumberValue inside the player.

2 Likes

I changed the script and added the new bit of code in, but it still doesn’t work and doesn’t print out any errors to show that it isn’t working, but it still doesn’t save for some reason.

This is what the full script looks like now:

local DataStoreService = game:GetService("DataStoreService")
local playerData = DataStoreService:GetDataStore("PlayerData")

local function onPlayerJoin(player) 
	--other leaderstats go here
	local upgrades = Instance.new("NumberValue") 
	upgrades.Name = "Multiplier"
	upgrades.Parent = player

	local playerUserId = "Player_" .. player.UserId  
	local data = playerData:GetAsync(playerUserId)  

	if data then
		--other leaderstats go here
		upgrades.Value = data['Multiplier']
		if upgrades.Value <= 0 then
			upgrades.Value = 1
		end
	else 
		upgrades.Value = 1
	end
end

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.leaderstats:GetChildren()) do
		player_stats[stat.Name] = stat.Value
	end
	return player_stats
end

local function onPlayerExit(player)  
	local player_stats = create_table(player)
	local success, err = pcall(function()
		local playerUserId = "Player_" .. player.UserId
		playerData:SetAsync(playerUserId, player_stats) 
	end)

	local PlayerService = game:GetService("Players")

	game:BindToClose(function()
		for _, player in pairs(PlayerService:GetPlayers()) do
			coroutine.wrap(onPlayerExit)(player) -- stops yielding
		end
	end)

	if not success then
		warn('Could not save data!')
	end
end

game.Players.PlayerAdded:Connect(onPlayerJoin)
game.Players.PlayerRemoving:Connect(onPlayerExit)
2 Likes
local DataStoreService = game:GetService("DataStoreService")
local playerData = DataStoreService:GetDataStore("PlayerData")

local function onPlayerJoin(player) 
	--other leaderstats go here
	local upgrades = Instance.new("NumberValue") 
	upgrades.Name = "Multiplier"
	upgrades.Parent = player
	upgrades.Value = 0

	local playerUserId = "Player_" .. player.UserId  
	local data = playerData:GetAsync(playerUserId)  

	if data then
		--other leaderstats go here
		--other leaderstats go here
		upgrades.Value = data.Value
		if upgrades.Value == 0 then
			upgrades.Value = 1
		end
	else 
		upgrades.Value = 1
	end
end

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.leaderstats:GetChildren()) do
		player_stats[stat.Name] = stat.Value
	end
	return player_stats
end

local function onPlayerExit(player)  
	local player_stats = create_table(player)
	local success, err = pcall(function()
		local playerUserId = "Player_" .. player.UserId
		playerData:SetAsync(playerUserId, player_stats) 
	end)

	if not success then
		warn('Could not save data!')
	end
end

game.Players.PlayerAdded:Connect(onPlayerJoin)
game.Players.PlayerRemoving:Connect(onPlayerExit)
game:BindToClose(function()
	for i, player in pairs(game.Players:GetChildren()) do
		onPlayerExit(player)
	end
end)

Try this and see if it works (make sure api services is enabled)

2 Likes

API services are enabled, but it still doesn’t save when I change the Multiplier value and then rejoin the game. Does the script work for you?

2 Likes
local DataStoreService = game:GetService("DataStoreService")
local playerData = DataStoreService:GetDataStore("PlayerData")

local function onPlayerJoin(player) 
	--other leaderstats go here
	local upgrades = Instance.new("NumberValue") 
	upgrades.Name = "Multiplier"
	upgrades.Parent = player
	upgrades.Value = 0

	local playerUserId = player.UserId.."_Player"
	local data = playerData:GetAsync(playerUserId)  

	if data then
		--other leaderstats go here
		--other leaderstats go here
		upgrades.Value = data.Value
		if upgrades.Value == 0 then
			upgrades.Value = 1
		end
	end
end

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.leaderstats:GetChildren()) do
		player_stats[stat.Name] = stat.Value
	end
	return player_stats
end

local function onPlayerExit(player)  
	local player_stats = create_table(player)
	local success, err = pcall(function()
		local playerUserId = player.UserId.."_Player"
		playerData:SetAsync(playerUserId, player_stats) 
	end)

	if not success then
		warn('Could not save data!')
	end
end

game.Players.PlayerAdded:Connect(onPlayerJoin)
game.Players.PlayerRemoving:Connect(onPlayerExit)
game:BindToClose(function()
	for i, player in pairs(game.Players:GetChildren()) do
		onPlayerExit(player)
	end
end)
2 Likes

What is the difference between…

upgrades.Value = data['Multiplier']

and

upgrades.Value = data.Value

? Just wondering.

2 Likes

same thing, just personal preferences

2 Likes

None of the data saves now, including the Multiplier value. Should the script be able to handle other data as well or does the script need to be changed in order for it to load in other stats too?

2 Likes
local DataStoreService = game:GetService("DataStoreService")
local playerData = DataStoreService:GetDataStore("PlayerData")

local function onPlayerJoin(player) 
	--other leaderstats go here
	local upgrades = Instance.new("NumberValue") 
	upgrades.Name = "Multiplier"
	upgrades.Parent = player
	upgrades.Value = 0

	local playerUserId = player.UserId.."_Player"
	local data = playerData:GetAsync(playerUserId)  

	if upgrades.Value == 0 then
		upgrades.Value = 1
	end
	if data then
		--other leaderstats go here
		--other leaderstats go here
		upgrades.Value = data.Value
	end
end

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.leaderstats:GetChildren()) do
		player_stats[stat.Name] = stat.Value
	end
	return player_stats
end

local function onPlayerExit(player)  
	local player_stats = create_table(player)
	local success, err = pcall(function()
		local playerUserId = player.UserId.."_Player"
		playerData:SetAsync(playerUserId, player_stats) 
	end)

	if not success then
		warn('Could not save data!')
	end
end

game.Players.PlayerAdded:Connect(onPlayerJoin)
game.Players.PlayerRemoving:Connect(onPlayerExit)
game:BindToClose(function()
	for i, player in pairs(game.Players:GetChildren()) do
		onPlayerExit(player)
	end
end)
2 Likes

Annoyingly, it doesn’t solve anything. It still won’t save any of the data now and it sets the Multiplier to 0 rather than the minimum of 1, of what it was used to be.

Edit: @lV0rd

if upgrades.Value == 0 then
		upgrades.Value = 1
	end

I want it to stay as 1, because without a number in Multiplier, you are effectively getting zero points.


When I use my previous format of upgrades.Value = data['Multiplier'] for all the other stats except the Multiplier which is stored inside the player, it saves all the other stats in the leaderstats alright. However, when I use the format of upgrades.Value = data.Value, nothing saves and nothing seems to works.

I will continue to use the previous format from now on, but it still doesn’t change the fact that the Multiplier value inside the player still doesn’t save.

2 Likes
local DataStoreService = game:GetService("DataStoreService")
local playerData = DataStoreService:GetDataStore("PlayerData")

local function onPlayerJoin(player) 
	local upgrades = Instance.new("NumberValue") 
	upgrades.Name = "Multiplier"
	upgrades.Parent = player
	upgrades.Value = 1

	local playerUserId = player.UserId.."_Player"
	local data = playerData:GetAsync(playerUserId)  

	if data then
		upgrades.Value = data['Multiplier']
		if upgrades.Value >= 0 then
			upgrades.Value = 1
		end
	end
end

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.leaderstats:GetChildren()) do
		player_stats[stat.Name] = stat.Value
	end
	return player_stats
end

local function onPlayerExit(player)  
	local player_stats = create_table(player)
	local success, err = pcall(function()
		local playerUserId = player.UserId.."_Player"
		playerData:SetAsync(playerUserId, player_stats) 
	end)

	if not success then
		warn('Could not save data!')
	end
end

game.Players.PlayerAdded:Connect(onPlayerJoin)
game.Players.PlayerRemoving:Connect(onPlayerExit)
game:BindToClose(function()
	for i, player in pairs(game.Players:GetChildren()) do
		onPlayerExit(player)
	end
end)

Does anyone else have any ideas about why it might not be saving inside the Player, or possibly a solution? Any help is appreciated :smile:.

2 Likes

There’s a question and statement that I have. My statement is that I would assume upgrades.Value = data.Value is not working over upgrades.Value = data["Multiplier"] because you are saving a table to your data and not a single stat, that I can see. So your data would actually look something similar to:

data = {
    ["Multiplier"] = 1,
    ["Stat2"] = 17,
    -- etc...
}

So doing data.Value returns nil and that’s why you have no data “saving” originally, but data["Multiplier"] works because it gets a specific data from the given table. Refer to your create_table function for that reason.

My question that I have is, are you trying to keep the multiplier as 1 no matter what or are you are trying to save it as 3 (for example) but it still returns as 1 on load? If the latter is the case (saving as 3 but loads as 1), your code is:

if data then
    upgrades.Value = data["Multiplier"]
    --[[
    You're testing if the value is greater than or equal to 0, if youre trying to 
    save as 3 it is getting reset back to 1. If you are getting 0 as your value 
    and not 1 on load, then your saving it the issue

    if upgrades.Value < 1 then
        upgrades.Value = 1
    end
    ]]--
    if upgrades.Value >= 0 then
        upgrades.Value = 1
    end
end
2 Likes

Whoops, I must have accidentally changed it to >=, I will correct the code and change it back. I want the Multiplier to be a minimum of 1 so you cannot save it when it is less than 1.

local DataStoreService = game:GetService("DataStoreService")
local playerData = DataStoreService:GetDataStore("PlayerData")

local function onPlayerJoin(player) 
	local upgrades = Instance.new("NumberValue") 
	upgrades.Name = "Multiplier"
	upgrades.Parent = player
	upgrades.Value = 1

	local playerUserId = player.UserId.."_Player"
	local data = playerData:GetAsync(playerUserId)  

	if data then
		upgrades.Value = data['Multiplier']
		if upgrades.Value <= 0 then
			upgrades.Value = 1
		end
	end
end

local function create_table(player)
	local player_stats = {}
	for _, stat in pairs(player.leaderstats:GetChildren()) do
		player_stats[stat.Name] = stat.Value
	end
	return player_stats
end

local function onPlayerExit(player)  
	local player_stats = create_table(player)
	local success, err = pcall(function()
		local playerUserId = player.UserId.."_Player"
		playerData:SetAsync(playerUserId, player_stats) 
	end)

	if not success then
		warn('Could not save data!')
	end
end

game.Players.PlayerAdded:Connect(onPlayerJoin)
game.Players.PlayerRemoving:Connect(onPlayerExit)
game:BindToClose(function()
	for i, player in pairs(game.Players:GetChildren()) do
		onPlayerExit(player)
	end
end)
1 Like

The issue that I see is that on loading, you are placing the stats in the player’s instance as value instances. However, when you go to save, you have to read those values back into the table so the table can be saved in the datastore.

I see that you are reading the values from the leader stats here:

However when you load the data in, I see this:

Multiplier is not parented to leaderstats, it’s parented to player, which is why your create table function doesn’t pick it up.

1 Like

Would I have to create another create table function for the NumberValue that is inside the player?

1 Like

No, you can nest tables and save it. I do it all the time. Something like this:

local function create_table(player)
	local player_stats = {
		leaderboard = {};
		data = {};
	}

	-- Saves value data that was parented to the player instance.
	-- Filters the types to make sure that only NumberValues are
	-- placed in the table because there are other things there that
	-- we don't want to save.
	for _, stat in pairs(player:GetChildren()) do
		if stat:IsA("NumberValue") then
			player_stats.data[stat.Name] = stat.Value
		end
	end

	-- Saves the leaderboard stats to a separate subtable so
	-- it can be loaded back in later from the datastore.
	for _, stat in pairs(player.leaderstats:GetChildren()) do
		player_stats.leaderboard[stat.Name] = stat.Value
	end

	return player_stats
end

Basically, I created two subtables. One is for the leaderstats. The other one is for the NumberValue instances that are stored under Player. I did this so that it’s easy to separate the data and place it where it belongs without too much hassle.

The DataStore service will serialize the table data into a format like JSON and save it to an SQL database (In fact, it might be JSON.). This is why you cannot store actual instances or functions, only data.

1 Like

Will the OnPlayerExit save both of the values, since the data saves when the player leaves the game or do I have to change it so it can save both? By the way, I added it in but I don’t think it saves any of the stats now.

1 Like

If you make the changes that I specified to your create_table() function, no other changes are necessary to save the data. You will need to make changes to load the data back in though, but it will be easy to do so.

You can do this:

local function createDataValues(instance, data)
	for name, value in pairs(data) do
		local nva = Instance.new("NumberValue")
		nva.Name = name
		nva.Value = value
		nva.Parent = instance
	end
end

local function loadData(player, stats)
	createDataValues(player, stats.data)
	createDataValues(player.leaderstats, stats.leaderstats)
end

That’s probably the simplest way to do it without coding every value. However, there is an algorithm that will walk the entire table and recreate the value structure that’s represented in the table verbatim. I haven’t had a need to develop it for Roblox though since I keep everything in tables and use a custom leaderboard.

1 Like