Datastores to save Dictionairies

Trying to find the easiest possible way to store say items a player has purchased that are linked to a certain class a player also has.

In depth explanation, game has multiple classes. Each class has multiple weapons (specific to that class) players can buy and equip. I am trying to save these purchases to the weapon, so I can just go into the ‘classes datastore’ and see which items they have purchased. For example:

['Knight'] = {
    ['Weapons'] = {'Classic Sword', etc...}

    ['Armour'] = {'Knight Armour', etc...}
}

['Archer'] = {
    ['Weapons'] = {'Bow', etc...}

    ['Armour'] = {'Archer Armour', etc...}
}

Also being able to track which item they have equipped, so they don’t have to re-equip when rejoining.

This is what I currently do (it just tracks what classes the player has unlocked, not weapons or anything like that)

local playerClasses = {player.UserId, {1, 2}}
local loadedClasses = classDataStore:GetAsync(player.UserId .. '-classes')

if loadedClasses ~= nil then
	playerClasses[2] = loadedClasses
end

table.insert(classData, playerClasses)

1 and 2 being ‘Knight’ and ‘Archer’ (trying to keep low data store values)

I think the main problem with trying to do this is I have trouble ‘visioning’ what a datastore actually looks like, so I then have trouble getting information out from it, as well as adding information into it. Not sure if there is a really big indepth tutorial on datastores (looked around here and read a couple of things, but still really confused)

1 Like

Found another post about datastore dictionaries:

Grabbed that code and edited it a little to fit my case example, here:

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

game.Players.PlayerAdded:Connect(function(player)
	local data = classDataStore:GetAsync(player.UserId) or {		
		Classes = {
			['Knight'] = {
				Weapon = 'Classic Sword', 
				Armour = 'Knight Armour', 
				Trail = 'None'
			},
			EquippedKnight = {
				Weapon = 'Classic Sword', 
				Armour = 'Knight Armour', 
				Trail = 'None'
			},
			
			['Archer'] = {
				Weapon = 'Bow and Arrow', 
				Armour = 'Archer Armour', 
				Trail = 'None'
			},
			EquippedArcher = {
				Weapon = 'Bow and Arrow', 
				Armour = 'Archer Armour', 
				Trail = 'None'
			}
		},		
		EquippedClass = 'Knight',
	}
end)

So basically, inside the [‘Knight’] would be all the items they own, and then the EquippedKnight lists what item the player has equipped. Obviously right now it’s set to the default equipped, but that can change when players equip other stuff. Can someone tell me if I am doing this right though? :sweat_smile: it seems convoluted, especially when in the future I want 15 or so classes, it’s gonna be a loooot of lines :sweat_smile:

Basically use json encodiing

local http = game:GetService("HttpService")
local data = 
{['Knight'] = {
    ['Weapons'] = {'Classic Sword', etc...}

    ['Armour'] = {'Knight Armour', etc...}
}

['Archer'] = {
    ['Weapons'] = {'Bow', etc...}

    ['Armour'] = {'Archer Armour', etc...}
}}
local data_encode = http:JSONEncode(data)
datastore:SetAsync(YourKey, data_encode)

When you load it up next time simply do

local data_encode = datastore:GetAsync(YourKey)
local data = http:JSONDecode(data_encode) --This will return the dictionary

Friendly literally mentioned this to me :sweat_smile: but why??

Never used JSON before, not entirely sure why. Is it just an easier way to store the data?

Yea, its definitely an easier way to store data. But if you wish to go off the hook you can create your own encoder with the string methods.

Yea no that sounds harder XD I’ll do the JSON thing :smiley: Also, where does that ‘data’ saving go?? Or is there no need for the DataStoreService??

EDIT nvm, i see it now XD

Yea sure there is a need for the service. I just shortened the script assuming that you understand what I mean.

1 Like

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