Cutstom Inventory Datastore not loading or (presumably) saving

Hi, ive written a datastore for an inventory that uses templates, and the items/information that are in them are in module scripts. my issue is that it doesnt seem that its saving, and its not loading any data.
Datastore script

local DataStoreService = game:GetService("DataStoreService")
local items = require(game.ReplicatedStorage.Items)
local player_data = DataStoreService:GetDataStore("player_data")
game.Players.PlayerRemoving:Connect(function(player)
	local inventory = {}
	for i, v in pairs(player:WaitForChild("PlayerGui").ScreenGui.background.inventory:GetChildren()) do
		table.insert(inventory,v.Name)
		local success, errorMessage = pcall(function()
			player_data:SetAsync(player.UserId,inventory)
		end)
		if success then
			print("Data Saved")
		else
			print("Error "..errorMessage)
		end
	end	
end)

game.Players.PlayerAdded:Connect(function(player)
	repeat
		task.wait()
	until player.Character
	task.wait(3)
	local success, inventory = pcall(function()
		return player_data:GetAsync(player.UserId)
	end)
	if success then
		if inventory then
			for i, v in pairs(inventory) do
				local item = items.Items(v)
				if item then
					local numb
					if item == "Dark Bandana" then
						numb = 1
					elseif	item == "Dark Shades" then
						numb = 2
					elseif item == "Fedora" then
						numb = 4
					elseif item == "Dramatic Mask" then
						numb = 3
					elseif item == "Old Boot" then
						numb = 6
					elseif item == "Red Bandana" then
						numb = 5
					elseif item == "Topaz Fedora" then
						numb = 7
					local clone = player.PlayerGui.ScreenGui.background.inventory.InventoryClient.Template:Clone()
						clone.Parent = player.PlayerGui.ScreenGui.background.inventory
					clone.Name = v
					clone.itemImage.Image = items.Items[numb].imageID
					clone.itemName.Text = items.Items[numb].name
						
					end
				end
			end
		end
		end
end)

Module Script

local m = {}

m.Rarities = {


	{
		name="Impossible";
		rarity=0.1;
		color=Color3.fromRGB(26, 27, 35);
	};
	{
		name="Mythical";
		rarity=0.3;
		color=Color3.fromRGB(255, 255, 0);
	};
	{
		name="Godly";
		rarity=1;
		color=Color3.fromRGB(198, 0, 3);
	};
	{
		name="Legendary";
		rarity=2;
		color=Color3.fromRGB(255, 179, 0);
	};
	{
		name="Epic";
		rarity=5;
		color=Color3.fromRGB(227, 83, 255);
	};
	{
		name="Rare";
		rarity=15;
		color=Color3.fromRGB(35, 149, 255);
	};
	{
		name="Uncommon";
		rarity=20;
		color=Color3.fromRGB(65, 208, 40);
	};
	{
		name="Common";
		rarity=35;
		color=Color3.fromRGB(193, 194, 195);
	};
}

m.Cases = {
		Free = {
			items = {
				"Dark Bandana";
				"Dark Shades";
				"Fedora";
				"Dramatic Mask";
				"Old Boot";
				"Red Bandana";
				"Topaz Fedora";
		
		}
	}
}

m.Items = {
	{
		name="Dark Bandana";
		imageID="17077089933";
		rarityType="Common";
		price=0.13;
	};
	{
		name="Dark Shades";
		imageID="17077078542";
		rarityType="Common";
		price=0.13;
	};
	{
		name="Dramatic Mask";
		imageID="17077078644";
		rarityType="Uncommon";
		price=0.24;
	};	
	{
		name="Fedora";
		imageID="17077078435";
		rarityType="Rare";
		price=0.51;
	};	
	{
		name="Red Bandana";
		imageID="17077090356";
		rarityType="Uncommon";
		price=0.24;
	};	
	{
		name="Old Boot";
		imageID="17077078756";
		rarityType="Epic";
		price=1.03;
	};	
	{
		name="Topaz Fedora";
		imageID="17077078905";
		rarityType="Impossible";
		price=115;
	};	
}

return m
2 Likes

Have you considered if “inventory” doesn’t exist when you use :GetAsync() because it’s the player’s first time playing? You need to set a default.

Here’s a bunch of other suggestions I have to improve your code as well:

Changes

Replace this:

repeat
    task.wait()
until player.Character

with:

player.CharacterAdded:Wait()

(I actually recommend just loading the data with :GetAsync() before waiting for the character because it’s asynchronous so you’d want it to be ready by the time they spawn in, you can take out the task.wait(3) as well)

local numb
if item == "Dark Bandana" then
	numb = 1
elseif item == "Dark Shades" then
	numb = 2
elseif item == "Fedora" then
	numb = 4
elseif item == "Dramatic Mask" then
	numb = 3
elseif item == "Old Boot" then
	numb = 6
elseif item == "Red Bandana" then
	numb = 5
elseif item == "Topaz Fedora" then
	numb = 7

You can instead do a dictionary (lookup table) and assign the item strings to a number.

Look into UpdateAsync and you can quickly just return a default in the function instead of specifying a default after doing :GetAsync() if the previous value was nil.

PlayerGui gets destroyed pretty much instantly when a player leaves the game, it tends to work in Studio but not on the Roblox platform.

Like @colorblindcrayon said, you need to set default stats for new players.

the character might load before this line is run, but I don’t see any need for the character in @cordedphoenix54 's script. Could just do

if not player.Character then player.CharacterAdded:Wait()
--or
local character = player.Character or player.CharacterAdded:Wait()







You should definitely use UpdateAsync to save data - you can utilise it to prevent data loss, and it has certain safety features that SetAsync doesn’t.

Anyway, the main thing I noticed is that the code is missing a BindToClose function. This means the game might be closing before it has a chance to save player data.

local players = game:GetService("Players")
local runService = game:GetService("RunService")

--Convert saving function to this format:
local function save(player)
end

players.PlayerRemoving:Connect(save)

--now, we add the BindToClose:

local function onClose()
    if runService:IsStudio() or #players:GetPlayers() <= 1 then task.wait(3) return nil end
    for _, player in ipairs(players:GetPlayers()) do
        save(player)
    end
    task.wait(3)
end

game:BindToClose(onClose)

TLDR:

  • PlayerGui is not a reliable place to read save data.
  • You need to set default stats for new players.
  • Use UpdateAsync instead of SetAsync (see this documentation)
  • Add a BindToClose to ensure the DataStoreService has time to save data

so with the playergui not being reliable, where do i save the data from

Try making a module inventory and keeping it on the server. That way, exploiters can’t modify it, you can control data the client receives, and you can take as long as you need to save the data. Just destroy it after you’re done to prevent outdated data saving.

sorry to be using alot of your time but how would i go by creating a module inventory

It’s honestly no problem.

I can’t make you the whole thing, but here’s a rough outline:

  • Have a template module that you clone and place into a folder in ServerScriptService. Make it identifiable (e.g. call it the player’s name).
  • Manage items in this module, for example consumables, weapons, etc.
  • When saving data, you should read from this module.
  • Whenever the client needs to access data, you can use a RemoteFunction so the server retrieves the data. This allows for more secure data.
  • Destroy this module after the player leaves to prevent other issues (if the player joins back to the same server, for example).

I’m sure there are some brilliant tutorials out there on YouTube. Go have a look!
I hope this helps :slight_smile: .

1 Like

Okay, i found a module inventory system and hooked it up to my system, but i still dont understand how to connect this to a datastore
module script

local Inventories = {}
local Inventory = {}

function Inventory:Remove(player, name, amount)
	if not player or not name then
		return -- Not going over this because I already did in the "new" function
	end
	local inventory = Inventory:Get(player)
	if not inventory then
		return -- The user doesn't have an inventory, we can't add an item
	end
	if inventory[name] then -- Check if the user has the item you're giving
		if inventory[name] - (amount or 1) < 0 then -- Check if the amount your removing is more then the user has
			inventory[name] = nil -- Delete the item completely
		else 
			inventory[name] = inventory[name] - amount or 1 -- Remove the amount or one item.
		end
	end
end

function Inventory:Add(player, name, amount)
	if not player or not name then
		return -- Not going over this because I already did in the "new" function
	end
	local inventory = Inventory:Get(player)
	if not inventory then
		return -- The user doesn't have an inventory, we can't add an item
	end
	if inventory[name] then -- Check if the user has the item you're giving
		inventory[name] = inventory[name] + amount or 1 -- They do, just add the amount or 1 
	else
		inventory[name] = amount or 1 -- They don't, give them the amount of items or 1
	end
end

function Inventory:Get(player)
	if not player then
		return -- Not going over this because I already did in the "new" function
	end
	return Inventories[player] -- Return the cached inventory!
end

function Inventory.new(player, contents) -- Hey! Our new function, it takes a player and contents.
	if not player then
		return -- The player that we specified doesn't exist.
	end
	if not contents then -- We have no contents, let's make some.
		contents = { -- Setting our contents to a table
			
		}
	end
	Inventories[player] = contents -- Assign the users contents to our cache
	return Inventories[player] -- return the inventory, (semi useless b/c its just content but still)
end

return Inventory

–local script

local UserInputService = game:GetService("UserInputService")

local List = script.Parent
local module = require(game.ReplicatedStorage.Items)
local b = require(game.Players.LocalPlayer:WaitForChild("PlayerGui"):WaitForChild("Modules"):WaitForChild("case"))
local inventory_module = require(game.ReplicatedStorage.ModuleInv)
local template = game.ReplicatedStorage.ModuleInv:WaitForChild("Template")

function addItem(player, name, amount)
	inventory_module:Add(player, name, amount)
	
end
function removeItem(player, name, amounnt)
	inventory_module:Remove(player, name, amounnt)
end
	
	local player = game.Players
	game.ReplicatedStorage.Cases.GetItem.OnClientEvent:Connect(function(unboxed)
		print(unboxed)
		addItem(player, unboxed, 1)
		local button = template:Clone()
		button.Name = module.Items[unboxed].name
		button.Visible = true
		button.Parent = List
	button.ImageLabel.Image = "rbxassetid://"..module.Items[unboxed].imageID
	
	end)
1 Like

When getting the data to save, you can just call a function in your inventory module that will collect all the player’s data and return it. Then, just save it to the store.

im sorry to bother you more, but i have little experience in using remote functions, so i dont know how to collect and load the data

Create a RemoteFunction in ReplicatedStorage. Let’s call it “GetData”.

On the client:

local rStorage = game:GetService("ReplicatedStorage")
local dataFunc = rStorage:WaitForChild("GetData")

--we can invoke the function to get the data.
local data = dataFunc:InvokeServer()

On the server:

local rStorage = game:GetService("ReplicatedStorage")
local ssService = game:GetService("ServerScriptService")

local dataFunc = rStorage.GetData

local function retrieve(player)
    local inv = require(ssService.Inventories[player.Name])
    return inv:Get()
end

dataFunc.OnServerInvoke = retrieve

okay, im really sorry im basically getting you to do this for me but now how would i use the data to create templates and store it once the player leaves

Pretty sure this function is how you would get the player’s data. I can’t help with how to unpack it, that is specific to your game.

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.