Adding a new class screws up data saves

Hello,
In my class-based game, each class has its own stats like wins, kills, etc. When I add a new one, some data from other classes gets mixed up and put into classes the player has never played as before.

I know it’s because I have folders arranged with each class name, and the data save script uses a for loop to go through each of these class folders and create data folders in the player, each named after the class.

The main problem is this: for example, I have a class with a name that starts with a C (C class), and if I add a class whose name starts with a B (B class), the C class’ stats become the B class’ stats, because the folders are automatically arranged alphabetically by roblox.

I honestly don’t know how to solve this…

local DS = game:GetService("DataStoreService"):GetDataStore("ClassStats")

game.Players.PlayerAdded:Connect(function(p)
	local fo = Instance.new("Folder",p)
	fo.Name = "ClassStats"

	
	for i, v in pairs(game.ServerStorage.Classes:GetChildren()) do
		local f = Instance.new("Folder", fo)
		f.Name = v.Name
		
		
		local w = Instance.new("IntValue",f)
		w.Name = "Wins"
		
		local k = Instance.new("IntValue",f)
		k.Name = "Kills"
		
		local d = Instance.new("IntValue",f)
		d.Name = "Deaths"
		
		local t = Instance.new("IntValue",f)
		t.Name = "Time Played"
		
		local r = Instance.new("IntValue",f)
		r.Name = "Rounds"
		
		local wr = Instance.new("IntValue",f)
		wr.Name = "Win Rate"
		
		local ws = Instance.new("IntValue",f)
		ws.Name = "Highest Winstreak"
		
		local Time = Instance.new("IntValue",f)
		Time.Name = "TickTime"
		Time.Value = tick()
	end
	
	wait(1)
	local plrKey = "stat_"..p.UserId
	
	local GetSaved
	
	local function GetData()
		local sucess, err = pcall(function()	
			GetSaved = DS:GetAsync(plrKey)
		end)
	
		if GetSaved then
			local num = 0
		
------------------------- The class stat loading is here ---------------------------------
			for i, v in pairs (p.ClassStats:GetChildren()) do
				for i, e in pairs(v:GetChildren()) do
					if e.Name ~= "TickTime" then
						num = num + 1
						e.Value = GetSaved[num]
					end
				end
			end
			
			
			num = num + 1
			ptg.Value = GetSaved[num]
		
			local L = Instance.new("BoolValue")
			L.Name = "loaded"
			L.Parent = p
			warn("Data Loaded")
		end
	end
	
		GetData()
end)

Note: this is not the whole script, but this is the part that loads data and is therefore what needs fixing.

i believe this is the problem as you should reference a name like “ClassRounds” instead of just a number since it can get messed up from orders and what not

sounds good, but how would I go about making the number return to normal (without the class name in front of it) when the player loads into the game?

what do you mean by return to normal?

so, say I add the class name before the number (for example: Class24) when the player leaves the game so that the data won’t be messed up. When they join, I want the Class name before it to go away (Class24 > 24). How would I go about doing that? Is there a string function that could help me there?

A better method for this instead of storing them into a table using a number as the index, would be to just use a string as an Index

I.E.

local data = {}

data["StringName"] = 0

“StringName” being the actual index

If all you do is 'Class'..classNum for the key
Then string.sub() is your fix.

classNum = string.sub(key,6)

I’m a little confused here. How would I use this in the context of this script here? Why would I need an extra table?

If you don’t have much to lose, data wise.

Consider scope to better organize your datastores and avoid convoluted naming.
It would be especially useful if the class is picked before joining play and all data updates are unique to a class. (separate save files)

Notice how here

num = num + 1
e.Value = GetSaved[num]

GetSaved is a table, and num is an index in the table,
you can instead use the name of each value as an index in the datastore table and save and load it that way in which case that line would turn into

e.Value = GetSaved[v.Name]

but doing it this way you’d be resetting data that used the previous method, and additonally will have to change how it saves as well.

hmm… it seems it won’t really load when the player joins the game. I don’t know if it’s a loading or saving problem. I think it’s a saving problem because I don’t think the string index is saved in the table here

game.Players.PlayerRemoving:Connect(function(p)
	local Table = {}
	
	local f = p.ClassStats[p.Class.Value]
	f["Time Played"].Value = f["Time Played"].Value + tick() - f.TickTime.Value
	
	for i, v in pairs (p.ClassStats:GetChildren()) do
		for i, e in pairs(v:GetChildren()) do		
			if e.Name ~= "TickTime" then
				Table[v.Name..e.Name] = e.Value
			end
		end
	end
	
	
	if p:FindFirstChild("loaded") then
		local success, err = pcall(function()
			DS:SetAsync("stat_"..p.UserId, Table)
		end)

		if success then
			warn("Saved Data")
		else
			warn(err)
		end
	end
end)

Just a couple things I figure i should go over;

Instead of doing

Table[v.Name..e.Name] = e.Value

you can do

Table[v.Name][e.Name]

This’ll make the datastore and code much cleaner by creating a table for each class, and then inserting your stats into each of those tables.

Second, your data isnt loading because it’s not saving to begin with. This is because of the following:

if p:FindFirstChild("loaded") then

above it’s looking for loaded to see if it exists, however it never exists when it’s a new player as seen in the following

if GetSaved then
	local num = 0
		
------------------------- The class stat loading is here ---------------------------------
	for i, v in pairs (p.ClassStats:GetChildren()) do
		for i, e in pairs(v:GetChildren()) do
			if e.Name ~= "TickTime" then
				num = num + 1
				e.Value = GetSaved[num]
			end
		end
	end
			
			
	num = num + 1
	ptg.Value = GetSaved[num]
		
	local L = Instance.new("BoolValue")
	L.Name = "loaded"
	L.Parent = p
	warn("Data Loaded")
end

you can easily fix this by adding an else to this and creating the loaded object in the case of it being new player data

Edit: Just a bit of a reminder, but don’t forget to implement the new saving methods into your loading too.

Alright so I have a way for new players to have the “loaded” thing, and I tried making tables for each class inside the table, however it doesn’t seem to be saving or loading.

The code that’s supposed to make it load:

if GetSaved then
	local num = 0
		
------------------------- The class stat loading is here ---------------------------------
	for i, v in pairs (p.ClassStats:GetChildren()) do
		for i, e in pairs(v:GetChildren()) do
			if e.Name ~= "TickTime" then
				local t = {}
				t = GetSaved[v.Name] --- for some reason, this is nil
				e.Value = GetSaved[v.Name][e.Name]
			end
		end
	end
			
			
	num = num + 1
	ptg.Value = GetSaved[num]
		
	local L = Instance.new("BoolValue")
	L.Name = "loaded"
	L.Parent = p
	warn("Data Loaded")
end

The code that’s supposed to make it save:

game.Players.PlayerRemoving:Connect(function(p)
	local Table = {}
	
	local f = p.ClassStats[p.Class.Value]
	f["Time Played"].Value = f["Time Played"].Value + tick() - f.TickTime.Value
	
	for i, v in pairs (p.ClassStats:GetChildren()) do
		for i, e in pairs(v:GetChildren()) do		
			if e.Name ~= "TickTime" then
				Table[v.Name] = {}
				Table[v.Name][e.Name] = e.Value
			end
		end
	end
	
	
	if p:FindFirstChild("loaded") then
		local success, err = pcall(function()
			DS:SetAsync("stat_"..p.UserId, Table)
		end)

		if success then
			warn("Saved Data")
		else
			warn(err)
		end
	end
end)