Stats not saving

So uhm, i was making stats for my game, and added the system and stuff, decided to make the stats save, everything works no errors, levels and xp saves but not the stats, idk why, pls help.

local DSS = game:GetService("DataStoreService")
local mainDS = DSS:GetDataStore("EzDataStore")

local levelEvent = RS:WaitForChild("LevelUp")

local function loadPlayerData(player)
    local data
    local success, errorMsg = pcall(function()
        data = mainDS:GetAsync(player.UserId)
    end)
    if not success then
        warn("error, cant recover player data " .. player.Name .. ": " .. errorMsg)
        return
    end

    if data then
        local leaderstats = player:FindFirstChild("leaderstats")
        local statsFolder = player:FindFirstChild("statsFolder")

        if leaderstats then
            local level = leaderstats:FindFirstChild("Lvl")
            local exp = leaderstats:FindFirstChild("Exp")
            local reqexp = player:FindFirstChild("RequiredExp")

            if level then level.Value = data[1] or level.Value end
            if exp then exp.Value = data[2] or exp.Value end
            if reqexp then reqexp.Value = data[3] or reqexp.Value end
        end

        if statsFolder then
            local strength = statsFolder:FindFirstChild("strength")
            local dexterity = statsFolder:FindFirstChild("dexterity")
            local wits = statsFolder:FindFirstChild("wits")
            local luck = statsFolder:FindFirstChild("luck")

            if strength then strength.Value = data[4] or strength.Value end
            if dexterity then dexterity.Value = data[5] or dexterity.Value end
            if wits then wits.Value = data[6] or wits.Value end
            if luck then luck.Value = data[7] or luck.Value end
        end
    end
end

local function savePlayerData(player)
    local leaderstats = player:FindFirstChild("leaderstats")
    local statsFolder = player:FindFirstChild("statsFolder")

    if leaderstats and statsFolder then
        local level = leaderstats:FindFirstChild("Lvl")
        local exp = leaderstats:FindFirstChild("Exp")
        local reqexp = player:FindFirstChild("RequiredExp")
        
        local strength = statsFolder:FindFirstChild("strength")
        local dexterity = statsFolder:FindFirstChild("dexterity")
        local wits = statsFolder:FindFirstChild("wits")
        local luck = statsFolder:FindFirstChild("luck")

        local data = {
            level and level.Value or 1,
            exp and exp.Value or 0,
            reqexp and reqexp.Value or 100,
            strength and strength.Value or 1,
            dexterity and dexterity.Value or 1,
            wits and wits.Value or 1,
            luck and luck.Value or 1
        }

        local success, errorMsg = pcall(function()
            mainDS:SetAsync(player.UserId, data)
        end)
        if not success then
            warn("Error during saving " .. player.Name .. ": " .. errorMsg)
        end
    end
end

game.Players.PlayerAdded:Connect(function(player)
    local leaderstats = Instance.new("Folder", player)
    leaderstats.Name = "leaderstats"

    local level = Instance.new("NumberValue", leaderstats)
    level.Name = "Lvl"
    level.Value = 1

    local exp = Instance.new("NumberValue", leaderstats)
    exp.Name = "Exp"
    exp.Value = 0

    local requiredExp = Instance.new("NumberValue", player)
    requiredExp.Name = "RequiredExp"
    requiredExp.Value = level.Value * 100

    local statPoints = Instance.new("IntValue", player)
    statPoints.Name = "statPoints"
    statPoints.Value = 1

    local statsFolder = Instance.new("Folder", player)
    statsFolder.Name = "statsFolder"

    local strength = Instance.new("IntValue", statsFolder)
    strength.Name = "strength"
    strength.Value = 1

    local dexterity = Instance.new("IntValue", statsFolder)
    dexterity.Name = "dexterity"
    dexterity.Value = 1

    local wits = Instance.new("IntValue", statsFolder)
    wits.Name = "wits"
    wits.Value = 1

    local luck = Instance.new("IntValue", statsFolder)
    luck.Name = "luck"
    luck.Value = 1

    loadPlayerData(player)

    local statsGui = player.PlayerGui:WaitForChild("StatTestUI")
    statsGui.Frame.StatPoints.Text = statPoints.Value
    statsGui.Frame.Stats.Strength.statValue.Text = strength.Value
    statsGui.Frame.Stats.Dexterity.statValue.Text = dexterity.Value
    statsGui.Frame.Stats.Wits.statValue.Text = wits.Value
    statsGui.Frame.Stats.Luck.statValue.Text = luck.Value

    exp.Changed:Connect(function()
        if exp.Value >= requiredExp.Value then
            exp.Value = 0
            level.Value = level.Value + 1
            requiredExp.Value = level.Value * 100
        end
    end)

    level.Changed:Connect(function()
        statPoints.Value = statPoints.Value + 3
        levelEvent:FireClient(player)
    end)
end)

game.Players.PlayerRemoving:Connect(function(player)
    savePlayerData(player)
end)

1 Like

This is too much unorganized code but from what I can see this would be bad performance wise.

You are connecting an event to a function every time a player is added to the game, which would cause memory leaks.

Memory leak on the server, which would be particularlly bad because servers can last for days or weeks before ending. Which can make it feel like players are experiencing the problem everywhere.

You can simply use these events on a local script instead, and use remote events.

local Player = game.Players.LocalPlayer
local leaderstats = Player.leaderstats

leaderstats.level.Changed:Connect(function()
-- Fire remote events here and I think you know what to do
end)

Which would also prevent these memory leaks from happening by the way.

Edit: I forgot that the player/character does get deleted when they leave, but still.

I don’t know what you mean by stats not saving (if they are nil, 0 or 1), but I am generally unfamiliar with having Boolean operations when setting variables (in the table). I feel like you can just do:

local data = {
            ["level"] = level.Value
            ["exp"] = exp.Value
            ["reqexp"] = reqexp.Value
            ["strength"] = strength.Value
            ["dexterity"] = dexterity.Value
            ["wits"] = wits.Value
            ["luck"] = luck.Value
        }

if you are worried about any of the values being nil, then that is not a problem that should be tackled when saving the data. Rather you should make sure that the values are in the stats folder, you should be able to keep track of them if nothing else removes them.

Are you sure this will happen? Having two events connected to a player should not be a problem, even if there are 50 players in the game, only 100 events will be connected, and I have seen games with more than 100 events. I feel like the garbage collector should pick up the dropped events, are you saying that he should use :Disconnect() so that the events don’t accumulate?
I don’t think there is anything wrong with your suggestion, but I do not think it will cause much of a performance boost.

so i went back and used the old code i had, and this is what it does now:

local RS = game:GetService("ReplicatedStorage")
local levelEvent = RS:WaitForChild("LevelUp")

local DSS = game:GetService("DataStoreService")
local mainDS = DSS:GetDataStore("EzDataStore")

game.Players.PlayerAdded:Connect(function(plr)
	plr.CharacterAdded:Connect(function(char)
		local leaderstats = Instance.new("Folder", plr)
		leaderstats.Name = "leaderstats"

		local level = Instance.new("NumberValue", leaderstats)
		level.Name = "Lvl"
		level.Value = 1

		local Exp = Instance.new("NumberValue", leaderstats)
		Exp.Name = "Exp"
		Exp.Value = 0

		local RequiredExp = Instance.new("NumberValue", plr)
		RequiredExp.Name = "RequiredExp"
		RequiredExp.Value = level.Value * 100

		local statPoints = Instance.new("IntValue", plr)
		statPoints.Name = "statPoints"
		statPoints.Value = 1

		local statsFolder = Instance.new("Folder", plr)
		statsFolder.Name = "statsFolder"

		local strength = Instance.new("IntValue", statsFolder)
		strength.Name = "strength"
		strength.Value = 1

		local dexterity = Instance.new("IntValue", statsFolder)
		dexterity.Name = "dexterity"
		dexterity.Value = 1

		local wits = Instance.new("IntValue", statsFolder)
		wits.Name = "wits"
		wits.Value = 1

		local luck = Instance.new("IntValue", statsFolder)
		luck.Name = "luck"
		luck.Value = 1

		local statsGui = plr.PlayerGui:WaitForChild("StatTestUI")
		statsGui.Frame.StatPoints.Text = statPoints.Value
		statsGui.Frame.Stats.Strength.statValue.Text = strength.Value
		statsGui.Frame.Stats.Dexterity.statValue.Text = dexterity.Value
		statsGui.Frame.Stats.Wits.statValue.Text = wits.Value
		statsGui.Frame.Stats.Luck.statValue.Text = luck.Value

		Exp.Changed:Connect(function()
			if Exp.Value >= RequiredExp.Value then
				Exp.Value = 0
				level.Value += 1
				RequiredExp.Value = level.Value * 100
			end
		end)

	

		local data = mainDS:GetAsync(plr.UserId)
		if data then
			if data[1] then level.Value = data[1] end
			if data[2] then Exp.Value = data[2] end
			if data[3] then RequiredExp.Value = data[3] end
			if data[4] then strength.Value = data[4] end
			if data[5] then dexterity.Value = data[5] end
			if data[6] then wits.Value = data[6] end
			if data[7] then luck.Value = data[7] end
			if data[8] then statPoints.Value = data[8] end
		end
	end)
end)

game.Players.PlayerRemoving:Connect(function(player)
	local leaderstats = player:WaitForChild("leaderstats")
	local lvl = leaderstats:WaitForChild("Lvl")
	local exp = leaderstats:WaitForChild("Exp")
	local reqexp = player:WaitForChild("RequiredExp")

	local statsFolder = player:WaitForChild("statsFolder")
	local strength = statsFolder:WaitForChild("strength")
	local dexterity = statsFolder:WaitForChild("dexterity")
	local wits = statsFolder:WaitForChild("wits")
	local luck = statsFolder:WaitForChild("luck")
	local statPoints = player:WaitForChild("statPoints")

	local data = { 
		lvl.Value,
		exp.Value,
		reqexp.Value,
		strength.Value,
		dexterity.Value,
		wits.Value,
		luck.Value,
		statPoints.Value
	}

	local success, errorMsg = pcall(function()
		mainDS:SetAsync(player.UserId, data)
	end)

	if not success then
		warn("Failed to save data for player " .. player.Name .. ": " .. errorMsg)
	end
end)

I tried this too but it didn’t work

At least in this case, you’re setting the UI Values before you even load your data.

local data = mainDS:GetAsync(plr.UserId)
if data then
	if data[1] then level.Value = data[1] end
	if data[2] then Exp.Value = data[2] end
	if data[3] then RequiredExp.Value = data[3] end
	if data[4] then strength.Value = data[4] end
	if data[5] then dexterity.Value = data[5] end
	if data[6] then wits.Value = data[6] end
	if data[7] then luck.Value = data[7] end
	if data[8] then statPoints.Value = data[8] end
end

local statsGui = plr.PlayerGui:WaitForChild("StatTestUI")
statsGui.Frame.StatPoints.Text = statPoints.Value
statsGui.Frame.Stats.Strength.statValue.Text = strength.Value
statsGui.Frame.Stats.Dexterity.statValue.Text = dexterity.Value
statsGui.Frame.Stats.Wits.statValue.Text = wits.Value
statsGui.Frame.Stats.Luck.statValue.Text = luck.Value

Exp.Changed:Connect(function()
	if Exp.Value >= RequiredExp.Value then
		Exp.Value = 0
		level.Value += 1
		RequiredExp.Value = level.Value * 100
	end
end)
1 Like

Are you changing the stat values in a local script?

statsFolder.strength.Value += 1 -- doing something like this in a local script
local button = script.Parent
local plr = game.Players.LocalPlayer
local statsFolder = plr:WaitForChild("statsFolder")
local statsPoints = plr:WaitForChild("statPoints")
local stat = statsFolder:WaitForChild("dexterity")

button.MouseButton1Click:Connect(function()
	if statsPoints.Value > 0 then
		statsPoints.Value -= 1
		stat.Value += 1
	end
end)

I know what is going on, This is a classic Server-Client problem. You are changing the stats value in a client script. Which does not replicate to the server, then when you are saving the data. The server saves the server values, not the client values. What I recommend you do is use a remote event to fire from the client to the server, that then (in a server script) changes the stat values.
Something like this:
ServerScript:

game.ReplicatedStorage.StatsUpdater.OnServerEvent:Connect(function(Stat,NewValue)
	Stat.Value = NewValue
end)

ClientScript:

button.MouseButton1Click:Connect(function()
	game.ReplicatedStorage.StatsUpdater:FireServer(Stat,NewValue)
end)

Here are some resources:

o wow, even with resources, tysm, ill see if it works

yeah no, still doesnt work, like, it changes the value, but it doesnt save it, i think im just gonna rewrite the whole datastore code at this point, not worth spending more time on it tryna figure out whats wrong

even if it’s doesn’t work, tysm for the help :>

Ok, welp, sorry that you couldent get it to work. Just a few more tips on debugging so if this happens again you will be more prepared.
Use print statements in order to make sure that the variables are correct. (print(luck), print(data), print(leaderstats) etc)
Use Breakpoints and Watch, a more advanced way to look at your code. The watch menu lets you look at every variable and function that your code has access to, and can make it very easy to verify that everything is running in the correct order.

1 Like

i just fixed it, and yes you where right about the Server-Client thingy, cuz only 1 button works properly. IDK HOW but it works now :>

code is almost identical
ut this one works

local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("CoolData")





--save data function
local function saveData(player)
	
	local tableToSave = {
		player.leaderstats.exp.Value;
		player.leaderstats.lvl.Value;
		player.requiredExp.Value;
		
		player.statsFolder.strength.Value;
		player.statsFolder.dexterity.Value;
		player.statsFolder.wit.Value;
		player.statsFolder.luck.Value;
		
		player.sp.Value
	}
	
	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)
	
	local leaderstats = Instance.new("Folder", player)
	leaderstats.Name = "leaderstats"

	local lvl = Instance.new("IntValue")
	lvl.Name = "lvl"
	lvl.Value = 1
	lvl.Parent = leaderstats


	local exp = Instance.new("IntValue")
	exp.Name = "exp"
	exp.Parent = leaderstats

	local requiredExp = Instance.new("IntValue")
	requiredExp.Name = "requiredExp"
	requiredExp.Value = lvl.Value * 100
	requiredExp.Parent = player


	--stats
	local statsFolder = Instance.new("Folder")
	statsFolder.Name = "statsFolder"
	statsFolder.Parent = player

	local sp = Instance.new("IntValue")
	sp.Name = "sp"
	sp.Value = 1
	sp.Parent = player

	local strength = Instance.new("IntValue")
	strength.Name = "strength"
	strength.Value = 1
	strength.Parent = statsFolder

	local wit = Instance.new("IntValue")
	wit.Name = "wit"
	wit.Value = 1
	wit.Parent = statsFolder

	local dexterity = Instance.new("IntValue")
	dexterity.Name = "dexterity"
	dexterity.Value = 1
	dexterity.Parent = statsFolder

	local luck = Instance.new("IntValue")
	luck.Name = "luck"
	luck.Value = 1
	luck.Parent = statsFolder
	
	local statsGui = player.PlayerGui:WaitForChild("StatTestUI")
	
	statsGui.Frame.StatPoints.Text = sp.Value
	statsGui.Frame.Stats.Strength.statValue.Text = strength.Value
	statsGui.Frame.Stats.Dexterity.statValue.Text = dexterity.Value
	statsGui.Frame.Stats.Wits.statValue.Text = wit.Value
	statsGui.Frame.Stats.Luck.statValue.Text = luck.Value
	
	exp.Changed:Connect(function(Changed)
		if exp.Value >= requiredExp.Value then
			exp.Value = 0
			lvl.Value += 1
			sp.Value += 4
			requiredExp.Value = lvl.Value * 100
		end
	end)
	
	
	local data
	local success, err = pcall(function()
		data = dataStore:GetAsync(player.UserId)
	end)
	
	if success and data  then
		exp.Value = data[1]
		lvl.Value = data[2]
		requiredExp.Value = data[3]
		
		strength.Value = data[4]
		dexterity.Value = data[5]
		wit.Value = data[6]
		luck.Value = data[7]
		
		sp.Value = data[8]
	else
		print("player has no data")
		
		exp.Value = 0
		lvl.Value = 1
		requiredExp.Value = 100

		strength.Value = 1
		dexterity.Value = 1
		wit.Value = 1
		luck.Value = 1
		
		sp = 1
	end
end)


game.Players.PlayerRemoving:Connect(function(player)
	saveData(player)
end)

game:BindToClose(function() --when server shutdowns
	for _, player in pairs(game.Players:GetPlayers()) do
		saveData(player)
	end
end)

this forum helped a lot

2 Likes

I forgot that the player/character objects are usually destroyed anyway so it’s fine. I was wrong about that. Events are quite cheap on memory anyway but they can accumulate over time.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.