How should I go around saving in-game item purchases?

Hey there! I’m Keanny, an upcoming developer currently developing on a game called “Globetrotter”. This question is reated to the game itself.

  1. What do you want to achieve?
    Saving item purchases. In this case, the items are cars. I want to be able to save car purchases into a table in the player, which is then saved into a datastore. Every time the player rejoins the game, the table will be loaded from the datastore. Then, the table is accessible to every part of the code using a BindableFunction.

For your information, there are 2 datastores in the game. One is the money datastore and the other is this. The money datastore works just fine.

Server Script Code:

local dataStoreService = game:GetService("DataStoreService")
local garageDataStore = dataStoreService:GetDataStore("moneyDataStore")

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

	local garage = {}
	garage.Parent = plr

	local garageData
	local success, failMessage = pcall(function()
		garageData = garageDataStore:GetAsync(game.Players.LocalPlayer.UserId.."-garage")
	end)
	
	if success then
		garage = garageData
	else
		print("whoops, failed to retrieve data.")
		warn(failMessage)
	end
end)

game.ReplicatedStorage:WaitForChild("CheckGarage").OnServerInvoke = function(player)
	return player.garage
end
  1. What is the issue?
    When I ran the code above, there was an error saying that “garage” is not a valid member of Player.

  2. What solutions have you tried so far?
    I searched up a lot of topics on tables and datastores (since I’ve never used them before) and I still don’t find any answers. I’ve checked the other datastore I have (which is the money datastore) and it works fine. (I did that just to confirm that I’ve implemented the datastore function correctly.

Any help would be much appreciated as this is my first post in the forums and this is the only thing left to finish the saving system. Thank you.

-Keanny

4 Likes

Apologies about the weird code format, this is my first time using it.

2 Likes

Please format the rest of your code, thanks.

4 Likes

DataStoreService is to be used on the server, you’re creating and loaded data from the client as well as creating new objects in a local script (client sided)

You’re then checking on the server if it exists; which it doesn’t due to being created on the client.

In other words, load, create, and parent your cars/data on the server rather then client.

2 Likes

It appears to me that you tried to create a table and parent it to the player, but this does not work. You will have to create a template table with the starting data.

local templateData = {
    Money = 0,-- You can change this later but this is only an example of what you can do
    Garage = {}
}

I noticed you tried to use game.Players.LocalPlayer.

You can not use game.Players.LocalPlayer on the server. If this is a client script, make sure you change it to a server script instead. Instead of game.Players.LocalPlayer, we use plr.UserId. We will also be creating a function to load the player data.

local function LoadPlayerData(plr)
	local PD = templateData -- If the player is new we will use the template data
	local success, err = pcall(function()
		local i = 0
		local playerSave = nil
		repeat 
			i = i + 1
			playerSave = garageDataStore:GetAsync(Pf .. plr.UserId)
			wait(1)
		until playerSave or i == 3 -- We use this to retry to get the data in case it fails.
		
		if playerSave then -- If a save is already found, we will override the template data with the save.
			PD = playerSave
		end
	end)
	return PD -- Return the new data
end

What we will do now is update the PlayerAdded function!

Players.PlayerAdded:Connect(function(plr)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = plr
	local LoadedData = LoadPlayerData(plr) -- Much more simple!
	local Garage = LoadedData.Garage 
	playerData[plr.Name] = LoadedData -- playerData is a table.
	for i, v in pairs(Garage) do
		print(v) --Print all the values in the garage table.
	end
end)

Now we must save the data when we leave, we will use UpdateAsync for this.

Players.PlayerRemoving:Connect(function(plr) 
	local success, err = pcall(function()
		garageDataStore:UpdateAsync(plr.UserId.."_garage",function(oldValue)
			local newValue = oldValue or templateData 
			local plrdata = playerData[plr.Name]
			if not plrdata then return end -- If their data doesn't exist then we exit the function.
			for i,data in pairs(plrdata) do -- We will update the old data with the current data.
				newValue[i] = data
			end
			return newValue
		end)
	end)
	if success then
		print("Saved data for "..plr.Name)
	else
		warn(err)
	end
end)

To update the garage, you will do:

table.insert(playerData[plr.Name].Garage,"Tesla Model S P100D") --My dream car :wink: 

For your event to check all the values in the garage, we will do

RepStorage:WaitForChild("CheckGarage").OnServerInvoke = function(plr)
	return playerData[plr.Name].Garage -- Return the table.
end

The same goes for anything in the table. I added a money value for demonstration purposes.

print(playerData[plr.Name].Money)

You can integrate anything else into that table.
Full code: Full code for the devforum dude - Pastebin.com

This was my first “tutorial” on here so I hope you found it useful and your learned from it! Please mark this as a solution if it solved your problem! :wink:

6 Likes

Just a tip, there is no need for you to have two separate datastores for such small data when a single datastore entry can hold over 200,000 characters in a single table, instead i recommend you save all the data under datastore in a table, one key can be used to store the car and one for the cash, like so:

local DataTable = {
["Cash"] =--cash here, 
["Cars"] = {}--[[subtable of cars here]],
}
2 Likes

Ok, where should all the code you suggested be? And what kind of script should the code be in?

1 Like

Since they’re using DataStoreService, it has to be in a Script. They even said to:

The best place to put Scripts is in ServerScriptService.

1 Like

Ok. So while we’re at it, I created a function that checks if the garage table has a specific car.
Code:

local function tableContains(toCheck, element)
for index = 1, #toCheck do
if toCheck[index] == element then
print(“True”)
return true
end
index = index + 1
end
end

The above code doesn’t work. Can someone please help me out on this one?
-Keanny

1 Like

You don’t need to add to index, the for loop does that for you:

local function tableContains(t, element)
    for i = 1, #t do
        if t[i] == element then
             return true
        end
    end
    return false
end

print(tableContains({1,2}, 1)) --true
print(tableContains({"ham", "cheese"}, "bread")) --false

They’re adding a table.find() function to do this for you soon, keep an eye out for it.

3 Likes

Please mark my post as a solution if it worked. Thanks

1 Like

oooh, interesting. Can’t wait for this line of code to make everything easier!

1 Like

…well yeah, I can do that but the end product will have over 600 cars so I’m afraid it wouldn’t work by then.

1 Like

so, you can just save there names or give each car a unique Id number and save that, it should be able to hold easily

1 Like

Ok, it works! Thank you! But now the money datastore is not working.
Code:

game.Players.PlayerAdded:Connect(function(plr)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = plr
	
	local Money = Instance.new("IntValue")
	Money.Name = "Money"
	Money.Parent = leaderstats
	
	local moneyData
	local success, failMessage = pcall(function()
		moneyData = moneyDataStore:GetAsync(tostring(plr.UserId).."-cash")
	end)
	if success then
		Money.Value = moneyData
		print(moneyData)
		print("tada!")
	else
		print("whoops!")
		warn(failMessage)
	end
end

game.Players.PlayerRemoving:Connect(function(plr)
	local success, failMessage = pcall(function()
		moneyDataStore:SetAsync(tostring(plr.UserId).."-cash",plr.leaderstats.Money.Value)
	end)
	if success then
		print("Done saving.")
	else
		print("whoops!")
		warn(failMessage)
	end
end

This is weird, can someone tell me what went wrong?

1 Like

You missed the parenthesis on the last end for both PlayerAdded/PlayerRemoving.
Here, I fixed it up for you:

local players = game:GetService("Players")

players.PlayerAdded:Connect(function(plr)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = plr
	
	local Money = Instance.new("IntValue")
	Money.Name = "Money"
	Money.Parent = leaderstats
	
	local moneyData
	local success, failMessage = pcall(function()
		moneyData = moneyDataStore:GetAsync(tostring(plr.UserId).."-cash")
	end)
	if success then
		Money.Value = moneyData
		print(moneyData)
		print("tada!")
	else
		print("whoops!")
		warn(failMessage)
	end
end)

players.PlayerRemoving:Connect(function(plr)
	local success, failMessage = pcall(function()
		moneyDataStore:SetAsync(tostring(plr.UserId).."-cash",plr.leaderstats.Money.Value)
	end)
	if success then
		print("Done saving.")
	else
		print("whoops!")
		warn(failMessage)
	end
end)
1 Like

…nope. The code you typed in just fixed a typo on the script. The original script is already what you’ve written.

Completely forgot to define the datastore, this definitely works now (I even tested it in studio):

local players = game:GetService("Players")
local dss = game:GetService("DataStoreService")
local moneyDataStore = dss:GetDataStore("Money")

players.PlayerAdded:Connect(function(plr)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = plr
	
	local Money = Instance.new("IntValue")
	Money.Name = "Money"
	Money.Parent = leaderstats
	
	local moneyData
	local success, failMessage = pcall(function()
		moneyData = moneyDataStore:GetAsync(plr.UserId.."-cash")
	end)
	if success then
		Money.Value = moneyData or 0
		print(moneyData)
		print("tada!")
	else
		print("whoops!")
		warn(failMessage)
	end
end)

players.PlayerRemoving:Connect(function(plr)
	local success, failMessage = pcall(function()
		moneyDataStore:SetAsync(plr.UserId.."-cash",plr.leaderstats.Money.Value)
	end)
	if success then
		print("Done saving.")
	else
		print("whoops!")
		warn(failMessage)
	end
end)

well yeah, it works but somehow the money added by driving is not changing the money value.
Code:

local transfer = require(script.Parent.Parent.Transfer) --This transfers the cashback, nothing to do with the problem
local interval = 1
local amount = transfer.cashback --let's say it's 300
local player = game.Players:GetPlayerFromCharacter(script.Parent.Parent)

while true do
	wait(interval)
	player.leaderstats.Money.Value = player.leaderstats.Money.Value + amount
end

The following code is placed in StarterCharacterScripts, inside another LocalScript which has no relation to the problem. The script itself is also a LocalScript.

You don’t change server sided values with a local script. You would have to use a server script for doing that. You can simply add the while true loop inside the player added function like this:

spawn(function()
	while true do
		wait(interval)
		player.leaderstats.Money.Value = player.leaderstats.Money.Value + amount
	end
end)

Let me know how it works for you!