Saving and Loading Inventory

does anyone know how make a script that saves and loads things inside a folder? I’ve struggled hard to make one myself
folder

4 Likes

I’m not entirely sure what you’re asking for, but it looks like this might be a case of wanting to save the stats with the player (so when a player rejoins your game, they get the same stats they had previously)?

If that’s the case, you’d be looking for Data Stores:
https://developer.roblox.com/articles/Data-store

1 Like

basically like a normal datastore script you can find in toolbox but saves everything inside a folder that’s not manually added, sorry if my English is hard to understand I’m not born English

Edit: improved the script a bit

The best way I can think of is to convert the inventory to a table, and then to encode that table and save it to a DataStore, as Thomas suggested. It will be pretty complicated (especially to account for all potential issues) but here’s a basic script for you to use (you may need to modify it depending on your system):

function save(player,inventory,datastore)
    local data={} --empty table
    for i,v in pairs(inventory:GetChildren()) do--use a "for" loop and GetChildren to cycle through
        table.insert(data,{Item=v.Name;Amount=v.Amount.Value} --convert item to table and add it to the master table
    end
    local success=pcall(datastore.SetAsync,tostring(player.userId),data)) --save it to a datastore
    if not success then print("save failed") end
end

function load(player,inventory,datastore)
    local success,data=pcall(datastore.GetAsync,tostring(player.userId) --get data
    if success then
	    data=data or {}
	    for i,v in pairs(data) do --loop through the data
	        --this entire block is dedicated to converting the table back to objects
	        local item=Instance.new("IntValue")
	        item.Name=v.Item
	        local amount=Instance.new("IntValue")
	        amount.Name="Amount"
	        amount.Value=v.Amount
	        amount.Parent=item
	        item.Parent=inventory
	    end
	else
		print("load failed")
	end
end

This may not function perfectly (I haven’t tested it at all) but hopefully it demonstrates the basics of how to set up a system like this. You’ll also want to add plenty of cases to protect from DataStore failures.

14 Likes

Don’t excuse for your non-understanding English. There are a lot of developers who don’t know English very well, but yet, they still got a huge knowledge in development.
Anyways, you will first of all need to learn what Data Stores are, because that’s the main part of making games. It’s really necessary.
You can start learning it on Roblox Developer Hub which ThomasChabot sent the link of it 3 replies above me :arrow_double_up:
If you want to learn more you can check out this link https://developer.roblox.com/articles/Saving-Player-Data
to learn how to properly save player data using Data Stores.

Learning Data Stores is really advanced, it can take from 1 - 4 weeks to learn it well.

2 Likes

I think you should use PCall() in case something wrong happens with DataStore.

2 Likes

Right, that’s definitely a good idea. Managing DataStore failures is a whole task in and of itself.

2 Likes

It’s not really necessary for you to encode a table to JSON before saving it. You can still save raw tables to data stores; they’re converted to strings or a JSON-like format under the hood. In addition, there’s a couple things to add on:

  • Data store calls should be wrapped in pcall, as anything else that’s internally a web call should be. The endpoints for these can fail and thus the methods would as well.
  • Using a pcall is good for handling data failures or no data. You typically want to avoid using the GetAsync or DefaultValue paradigm since it doesn’t account for load failures or nil data.
local success, data = pcall(DataStore.GetAsync, DataStore, Key)
if not success then
    -- Handle error
else
    if data then
        -- Handle loaded data
    else
        -- Handle players without data
    end
end

Finally, a personal recommendation - you should try and use UpdateAsync as much as possible when it comes to DataStore. You should very rarely ever call SetAsync and that should be in circumstances where you absolutely need to force a certain type of data to stick. UpdateAsync respects previous data and conflicting calls, whereas SetAsync does not. Improper use between Update and Set can be detrimental in several aspects, including UX.

Sorry to nitpick, I know that you provided a simple example, though these kinds of things should be known in case anyone happens to stumble across this thread and read the post as well. Knowledge of good practice when it comes to pivotal system features is a good thing to have.

Aside from all the above, you’re definitely right in what you say. I’m a personal fan of ValueObjects and I still convert ValueObjects to data tables for saving and vice versa. Works like a charm and it’s easy for my collaborators to jump on board if they have to do anything related to data.

The one thing in such a paradigm you need to be wary of is if all your data is not contained as immediate children of the data folder. That’s where hierarchy gets a little messy unless you have an adequate handler for it. I’m lazy so I use FindFirstChild with the recurse argument; in that regard, I also cannot use the same name for any of my data keys.

7 Likes

Hello there, sorry for bothering you again but do you know how to implement the pcall inside this script? I’ve been trying to figure out where do I put these pcall for about 2 hours already.

(I was about to write a lengthy post about pcalls but I just noticed @colbert2677 did… thanks!)

You must wrap (put a pcall around) GetAsync and SetAsync. If these error (it’s uncommon but it happens) it’s a big nusiance: data loss, game errors, the works. An example for wrapping SetAsync would be:

local success, err = pcall(function()
   datastore:SetAsync(tostring(player.userId),game:GetService("HttpService"):JSONEncode(data))
end)

if not success then
   error(err); -- You can use print or warn here if you please

   -- You have to find a way to handle the error
else
   -- In this case you don't really NEED an else but it's nice to have
end

DataStores are very hard to master as you must account for errors effectively and efficiently. That is why there are whole modules dedicated to help prevent data loss like DataStore2.

For me, DataStores are one of the biggest nuisances in programming a game. I really hope you find your solution and your DataStores run smoothly and without error!

1 Like

Seeing as your post is now the Solution, would you mind removing the JSONDecode and JSONEncoding part, add a pcall and other feedback provided by @colbert2677, users searching for this thread will most likely copypaste your answer and not notice the improvements made to it below your post.

2 Likes

For some reason the data isn’t loading and the pcall keeps returning an error, is something wrong In this script?

local datVersion = 4

local function save(player,inventory,datastore)
    local data={} --empty table
    for i,v in pairs(inventory:GetChildren()) do--use a "for" loop and GetChildren to cycle through
        table.insert(data,{Item=v.Name;Amount=v.Amount.Value}) --convert item to table and add it to the master table
    end
    local success=pcall(datastore.SetAsync,tostring(player.userId),data) --save it to a datastore
    if not success then print("save failed") end
end

game.Players.PlayerRemoving:Connect(function(player)
	local inventory = player("WaitForChild","PlayerInv")
	local key = player.UserId.."-player-"..datVersion
	local datastore = game:GetService("DataStoreService"):GetDataStore(key)
	save(player, inventory, datastore)
end)


local function load(player,inventory,datastore)
    local success,data=pcall(datastore.GetAsync,tostring(player.userId)) --get data
    if success then
	    data=data or {}
	    for i,v in pairs(data) do --loop through the data
	        --this entire block is dedicated to converting the table back to objects
	        local item=Instance.new("IntValue")
	        item.Name=v.Item
	        local amount=Instance.new("IntValue")
	        amount.Value=v.Amount
	        amount.Parent=item
	        item.Parent=inventory
	    end
	else
		print("load failed")
	end
end

game.Players.PlayerAdded:Connect(function(player)
	local inventory = player("WaitForChild","PlayerInv")
	local key = player.UserId.."-player-"..datVersion
	local datastore = game:GetService("DataStoreService"):GetDataStore(key)
	load(player, inventory, datastore)
end)

:GetDataStore(key) should not be a individual data store for each player. The key should be something like “Data”,“Data2”, or another relevant string. It does not need to be different for every player.

Service methods on Roblox are OOP-based. Calling Object:Method(args) is like calling Object.Method(Object, args). I see that you’ve made one mistake regarding the pcalls and that’s the arguments being passed.

Your pcalls are currently evaluating to datastore.Method(Key [, Value]). It needs to evaluate to datastore.Method(datastore, Key [, Value]) for dot syntax to work in an OOP environment (setting the self argument). Even outside of a pcall, this would need to be done.

In both your pcall statements, go back and add your datastore as the second argument after the method (first argument of the pcall).

-- In your save function
local success=pcall(datastore.SetAsync,datastore,tostring(player.UserId),data)

-- In your load function
local success,data=pcall(datastore.GetAsync,datastore,tostring(player.UserId))

I took the liberty of changing all userId fields to UserId, since the former is deprecated in favour of the latter for name convention consistency. I also kept your white space convention (which is not using any spaces at all) while rewriting those two lines.

1 Like

Yes it does need to be different for each player. If you aren’t changing the key, then you need to change the scope or the DataStore name itself. If you just use a single key, every single player is going to pull from the same key and data will not be individualised but rather global. That’s a huge mistake.

it saves successfully but It returns load fail in the load function, I added a warn(debug.traceback()) and it warns “Upvalue load”


local datVersion = 4

local function save(player,inventory,datastore)
    local data={} --empty table
    for i,v in pairs(inventory:GetChildren()) do--use a "for" loop and GetChildren to cycle through
        table.insert(data,{Item=v.Name;Amount=v.Amount.Value}) --convert item to table and add it to the master table
    end

    local success = pcall(datastore.SetAsync,datastore,tostring(player.UserId),data) --save it to a datastore
    if not success then warn(debug.traceback()) print("save failed") else print("Save Success") end
end

game.Players.PlayerRemoving:Connect(function(player)
	local inventory = player("WaitForChild","PlayerInv")
	local key = player.UserId.."-player-"..datVersion
	local datastore = game:GetService("DataStoreService"):GetDataStore(key)
	save(player, inventory, datastore)
end)


local function load(player,inventory,datastore)
    local success,data=pcall({datastore.GetAsync,datastore,tostring(player.UserId)})--get data
    if success then
	    data=data or {}
	    for i,v in pairs(data) do --loop through the data
	        --this entire block is dedicated to converting the table back to objects
	        local item=Instance.new("IntValue")
	        item.Name=v.Item
	        local amount=Instance.new("IntValue")
	        amount.Value=v.Amount
	        amount.Parent=item
	        item.Parent=inventory
	    end
else
	warn(debug.traceback())
		print("load failed")
	end
end

game.Players.PlayerAdded:Connect(function(player)
	local inventory = player("WaitForChild","PlayerInv")
	local key = player.UserId.."-player-"..datVersion
	local datastore = game:GetService("DataStoreService"):GetDataStore(key)
	load(player, inventory, datastore)
end)
local success,data=pcall({datastore.GetAsync,datastore,tostring(player.UserId)})--get data

Remove the curly brackets {} from your code and try again.

1 Like

I thought that scope was used later on. My mistake.

1 Like