Datastores to save Dictionairies

ahaah yea I completely read over the datastore part XD Quick thing though, obviously this needs to be setup when a player joins, but also loaded when the player joins.

local dataStoreService = game:GetService('DataStoreService')
local httpService = game:GetService('HttpService')

local classDataStore = dataStoreService:GetDataStore('ClassData', 'Test')

classData = {
	Classes = {
		['Knight'] = {
			['Weapons'] = {
				'Classic Sword',
			},
			['Armours'] ={
				'Knight Armour'
			} ,
			['Trails'] ={
				'None'
			} 
		},
		EquippedKnight = {
			Weapon = 'Classic Sword', 
			Armour = 'Knight Armour', 
			Trail = 'None'
		},
		
		['Archer'] = {
			['Weapons'] = {
				'Bow and Arrow'
			},
			['Armours'] ={
				'Archer Armour'
			} ,
			['Trails'] ={
				'None'
			} 
		},
		EquippedArcher = {
			Weapon = 'Bow and Arrow', 
			Armour = 'Archer Armour', 
			Trail = 'None'
		}
	},	
	EquippedClass = 'Knight',
}

game.Players.PlayerAdded:Connect(function(player)
	local setJSON = httpService:JSONEncode(classData)
	classDataStore:SetAsync(player.UserId, setJSON)
	
	local loadJSON = classDataStore:GetAsync(player.UserId)
	local data = httpService:JSONDecode(loadJSON)
end)

Little unsure if it would just load up that same default table that’s setup under classData. Which raises another question. Saving it. Surely just this would work??

game.Players.PlayerRemoving:Connect(function(player)
	local saveJSON = httpService:JSONEncode(classData)
	classDataStore:SetAsync(player.UserId, saveJSON)
end)

But I mean looking at this, looks like it’s just gonna save the table at the top of the script, and not the actual players one, which may have been edited.

What you did in the PlayerAdded is not needed in fact it just makes everything slower. so this is a better solution

game.Players.PlayerAdded:Connect(function(player)
	local loadJSON = classDataStore:GetAsync(player.UserId)
	local data = (loadJSON and httpService:JSONDecode(loadJSON)) or classData
end)

for the PlayerRemoving part this is correct only if the classData inside the script was changed, otherwise you are saving the same dictionary. So you need to create some kind of inventory system to load the current data for a specific player in the game. For example create a system that saves the dictionary for each player in the game and access it via a command like data[player.userId]

game.Players.PlayerRemoving:Connect(function(player)
	local saveJSON = httpService:JSONEncode(classData[player.userId])
	classDataStore:SetAsync(player.UserId, saveJSON)
end)

This is an edited script that I made for you

local dataStoreService = game:GetService('DataStoreService')
local httpService = game:GetService('HttpService')

local classDataStore = dataStoreService:GetDataStore('ClassData', 'Test')

local playersData = {}
local classData = {
	Classes = {
		['Knight'] = {
			['Weapons'] = {
				'Classic Sword',
			},
			['Armours'] ={
				'Knight Armour'
			} ,
			['Trails'] ={
				'None'
			} 
		},
		EquippedKnight = {
			Weapon = 'Classic Sword', 
			Armour = 'Knight Armour', 
			Trail = 'None'
		},
		
		['Archer'] = {
			['Weapons'] = {
				'Bow and Arrow'
			},
			['Armours'] ={
				'Archer Armour'
			} ,
			['Trails'] ={
				'None'
			} 
		},
		EquippedArcher = {
			Weapon = 'Bow and Arrow', 
			Armour = 'Archer Armour', 
			Trail = 'None'
		}
	},	
	EquippedClass = 'Knight',
}

game.Players.PlayerAdded:Connect(function(player)
	local loadJSON = classDataStore:GetAsync(player.UserId)
	local data = (loadJSON and httpService:JSONDecode(loadJSON)) or classData
    playersData[player.userId] = data
end)

game.Players.PlayerRemoving:Connect(function(player)
	local saveJSON = httpService:JSONEncode(playersData[player.userId])
	classDataStore:SetAsync(player.UserId, saveJSON)
end)

Now for example, if you want to change the EquippedKnight Weapon for Player1, you can do it like this

players_data[game.Players.Player1.userId].EquippedKnight.Weapon = 'Golden Sword'

That should be a basic inventory system. But I’d recommend making a more advanced one that can interact with the whole game.

4 Likes

Massive thanks!! :smiley:

What do you mean by ‘interact’ with the whole game though?

The current script is only accessible within the script itself. So you cant make changes from other scripts unless you share it globally which could be accomplished with modules or the global environment. Or make the dictionary an actual folder in the player instance and change it as values. Unless you decide to keep everything in one script you should be fine.

1 Like

Ahhh ok, well yeah, I am making everything modular, and being able to access from multiple scripts is gonna be a god send XD

Massive thank you again!! :smiley:

1 Like

You are welcome :slight_smile:

1 Like

Hey, uhh, just been testing and all of a sudden I’ve been getting this:

[Unable to cast value to std::string]

function classDataManager:GetData(player)
	local loadJSON = classDataStore:GetAsync(player.UserId) or classData
	print(loadJSON)
	local data = httpService:JSONDecode(loadJSON) -- Error here
	
    playersData[player.UserId] = data
end

Printing loadJson returns a table. The only changes I made to your code was put it into a module script.

function classDataManager:GetData(player) fires from a seperate module when a player joins. When I printed player and player.UserId it printed those correctly, so nothing to do with them :confused:

Im guessing the problem is that maybe because it’s never being Encoded??

local setJSON = httpService:JSONEncode(classData)
	classDataStore:SetAsync(player.UserId, setJSON)
	
	local loadJSON = classDataStore:GetAsync(player.UserId)
	local data = httpService:JSONDecode(loadJSON)

Is what was first mentioned, which obviously encodes it, then decodes it, but…

Which never has the Encode, so not sure if maybe this is why??

Sorry I made a mistake in here,

	local loadJSON = classDataStore:GetAsync(player.UserId) or classData

If there was nothing stored it would decode a table on the next line which is supposed to be a string

	local loadJSON = classDataStore:GetAsync(player.UserId)
	local data = (loadJSON and httpService:JSONDecode(loadJSON)) or classData

This should fix it :slight_smile:

2 Likes

Works :smiley:

Unfortunately, this means I gotta change a few things around my game with this conversion

function checkClass.OnServerInvoke(player, classID, equipping)
	repeat wait() until dataReady
	local user = playersData[player.UserId]
	if not user then return end
	
	for i, v in pairs(playersData) do
		if v[1] == player.UserId then
			for _, k in pairs(v[2]) do
				if k == classID then
					if equipping then
						user.EquippedClass = classStats[classID].Name
					end
					return true					
				end
			end
			return false
		end
	end
end

This function is suppose to be for a class menu, basically checks if the player 1 owns the class and 2 equipping it. Made changes to this so it can work as well. Just add [‘Owned’] so I can check if the player actually owns the kit in the first place.

local data = {
	Classes = {
		[1] = { -- Knight
			['Owned'] = true,
			['Weapons'] = {
				'Classic Sword',
			},
			['Armours'] ={
				'Knight Armour'
			} ,
			['Trails'] ={
				'None'
			} 
		},
		EquippedKnight = {
			Weapon = 'Classic Sword', 
			Armour = 'Knight Armour', 
			Trail = 'None'
		},
		
		[2] = {	-- Archer
			['Owned'] = true,
			['Weapons'] = {
				'Bow and Arrow'
			},
			['Armours'] ={
				'Archer Armour'
			} ,
			['Trails'] ={
				'None'
			} 
		},
		EquippedArcher = {
			Weapon = 'Bow and Arrow', 
			Armour = 'Archer Armour', 
			Trail = 'None'
		}
	},	
	EquippedClass = 'Knight',
	
	Level = 1,
	Exp = 0,
	Gold = 100,
	Gems = 0
}

The reason for the 1 as well instead of Knight is because of how I’ve connected it from another script which handles the classes stats. So on the line if k == classID then is basically the Client sending the class number (for example Knight is 1) which is the classID number. k should be the number it is in the data table.

How can I reference the players data from elsewhere in the script basically. Do I need to use JSON or simply just playersData[player.UserId].Classes??

Basically use playersData[player.UserId] as I mentioned before.

function checkClass.OnServerInvoke(player, classID, equipping)
    	repeat wait() until dataReady
    	local user = playersData[player.UserId]
    	if not user then return end
    	
    	for _, k in pairs(user.Classes) do
    		print(k)
    		if k.ID == classID then
    			if equipping then
    				user.EquippedClass = classStats[classID].Name
    			end
    			return true					
    		end
    	end
    	return false
    end

It prints a table, but I’m not entirely sure what table it’s printing. It’s printing it 4 times.

Classes = {
		['Knight'] = { -- Knight
			['ID'] = 1,
			['Owned'] = true,
			['Weapons'] = {
				'Classic Sword',
			},
			['Armours'] = {
				'Knight Armour'
			},
			['Trails'] = {
				'None'
			},
		},
		['Archer'] = {	-- Archer
			['ID'] = 2,
			['Owned'] = true,
			['Weapons'] = {
				'Bow and Arrow'
			},
			['Armours'] = {
				'Archer Armour'
			},
			['Trails'] = {
				'None'
			},
		},
	},

Not sure what’s going on. The function should basically just look to see if the Owned value inside the players data is set to true (obviously not mentioned in the script) but if k.ID ain’t printing 1 or 2 then, k.Owned will print nil as well.

Figured printing k should only print twice (Knight table/Archer table) and that k.ID would print whatever the ID was in the table. Same with k.Owned, k.Weapons, etc

Try doing more debugging, print every value inside the table and check if everything is going correctly. I dont see anything wrong with your script

Did this print:

print(user.Classes.Knight)

Which came back as a table. Tried this however;

print(user.Classes.Knight.ID)

Returned nil. Tried THIS though:

print(user.Classes.Knight.Weapons)

and it returned a table!!

To me, the print ID one should be returning whatever inside the ID, which is 1. When I try Owned, prints nil. The other 3, Weapons, Armour and Trails all print a table tho, so for some reason those are being picked up? While the ones that aren’t tables aren’t?

Tried this print as well:

print(user.Classes[1])

Which to my knowledge, should print the Knight table, as that’s the first part of the table. But that prints nil

Try running this

for i,v in next, user.Classes do
print(i,v)
if type(v) == 'table' then
for i,v in next, v do
print('\t',i,v)
end
end
end
 Archer table: 382A7B60
  	 Weapons table: 382A7D40
  	 Trails table: 382A7D70
  	 Armours table: 382A7E00
  EquippedArcher table: 382A77D0
  	 Trail None
  	 Armour Archer Armour
  	 Weapon Bow and Arrow
  Knight table: 382A7920
  	 Weapons table: 382A7950
  	 Trails table: 382A7BF0
  	 Armours table: 382A80D0
  EquippedKnight table: 382A78F0
  	 Trail None
  	 Armour Knight Armour
  	 Weapon Classic Sword

Not sure why the EquippedKnight and EquippedArcher are there as they are in there own table

Classes = {
	['Knight'] = { -- Knight
		['ID'] = 1,
		['Owned'] = true,
		['Weapons'] = {
			'Classic Sword',
		},
		['Armours'] = {
			'Knight Armour'
		},
		['Trails'] = {
			'None'
		},
	},
	['Archer'] = {	-- Archer
		['ID'] = 2,
		['Owned'] = true,
		['Weapons'] = {
			'Bow and Arrow'
		},
		['Armours'] = {
			'Archer Armour'
		},
		['Trails'] = {
			'None'
		},
	},
},

ClassEquips = {
	EquippedKnight = {
		Weapon = 'Classic Sword', 
		Armour = 'Knight Armour', 
		Trail = 'None'
	},
	EquippedArcher = {
		Weapon = 'Bow and Arrow', 
		Armour = 'Archer Armour', 
		Trail = 'None'
	},
},

EDIT OMG, the problem was the data store! I had it set to

local playerDataStore = dataStoreService:GetDataStore('PlayerDataStore', 'Test01')

Which Test01 had saved a completely different classData table :man_facepalming:
Far out I am sorry, I feel incredibly stupid right now

Can you try this

for i,v in next, user.Classes.Archer do
print(i,v)
if type(v) == 'table' then
for i,v in next, v do
print('\t',i,v)
end
end
end
for i,v in next, user.Classes.EquippedArcher do
print(i,v)
if type(v) == 'table' then
for i,v in next, v do
print('\t',i,v)
end
end
end

I suspect that the EquippedArcher was once saved on the datastore and is causing this problem now.

Just fixed it

Everything works… for now :sweat_smile:

Pretty sure there will be problems when it comes time to add in another class however. Would be ok for newer players, but since older players would already have a datastore, it’d load from their old data store, skipping that class out…?

For example, adding this into the table:

['Scout'] = {
			['ID'] = 3,
			['Owned'] = false,
			['Weapons'] = {
				'Dagger'
			},
			['Armours'] = {
				'Scout Armour'
			},
			['Trails'] = {
				'None'
			},
		},

Would be fine for new players, as theyd start their data from this default table, but the players with a datastore already wouldnt acquire this new section their data??

You need to create a function that checks the changes on the old to new data and then add it to the old data

…rip

Do you think you could have a look at this if you have some spare time? Long thread, could probs just read the OP and last few comments, but yeah, struggeling to comprehend what’s wrong