So to go through this a little more step by step, so to start, I would have a module script in ServerScriptService that has a table of every single players inventory.
Also, I have a more efficient and more readable way of tracking every single time each player’s data was changed.
I am not going to write the code for you, but instead give you a template of a system that works for saving each player’s inventory.
-- WITHOUT PROXYS
local module = {}
module.Profiles = {}
return module
This is where we will insert every player and their inventory. I recommend doing this instead of values since you don’t have to do instance.new every time a player joins the game, and it is easier to use keys to access the data.
I would parent the module to a folder known as “DataSave”, so that we know that this is where a module holding everybody’s saves are.
Next, I would create a server script in the same folder that manages the data that goes into the profile holding module.
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local InventoryDataStore = DataStoreService:GetDataStore("Inventory")
local DataHolder = require(script.Parent.Profiles)
These services are going to be uses to input each player’s inventory save into the module.
I am not going to go into the nitty-gritty of using profile service or data store, as there are already plenty tutorials on that, and I figure you already implemented one of the two into your game already.
So now we can use player added and player removed to add the player’s data save into the module.
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local InventoryDataStore = DataStoreService:GetDataStore("Inventory")
local DataHolder = require(script.Parent.Profiles)
Players.PlayerAdded:Connect(function(plr)
local success, err = pcall(function()
local PlayerInventorySave = InventoryDataStore:GetAsync(plr.UserId) -- Just getting their key which is their UserId so that we don't mix up data
DataHolder.Profiles[plr].Inventory = PlayerInventorySave or {} -- Now we are adding their save to the table
end)
end)
Again, I’m not going to go into the nitty-gritty of using datastore service or profile service, but the important thing that I’m doing here is adding the player’s save to the module so that it could be accessed by any script. The “or {}” just means that if the player’s data was equal to nil (had no data), then we would just insert a blank table.
Now for the player removed, we would just do the opposite.
Players.PlayerRemoving:Connect(function(plr)
DataHolder.Profiles[plr] = nil
end)
All we are doing is removing their data from the server.
Now I feel like this is the big question: How can we actually access the data from any script.
Well, now that’s very easy because since their inventory is already stored in a module, any time we want the data, we just require it.
I personally have another step that I usually take, and it’s just personal preference.
I will have another module that handles what goes on in each players inventory. I name that module, “DataFunctions” as it simply stores all the functions for the data.
For example, this is how it would work with the inventory:
function DF.AddItemToInventory(player, item)
local UserInventory = ProfileManager.Profiles[player].Data.Inventory -- you would just switch this up to wherever your table was if you were using DataStoreService
ProfileManager.Profiles[player].Data.Inventory[#UserInventory + 1] = item
return ProfileManager.Profiles[player].Data.Inventory
end
So from every ServerScript, let’s say the user earned a sword, I would just do.
local DataFunctions = require(script.Parent.DataFunctions)
-- Lets pretend that the user did some event to earn a sword
DataFunctions.AddItemToInventory(plr, sword)
Now, the next question would be, how do we send the inventory to the client every time a change happens if we are not using proxy’s. Well the answer is simple.
Since we already have a module function that adds the item to the inventory, inside that function, we could just fire an event that allows the client to update their inventory.
-- This is our data functions module
local RS = game:GetService("ReplicatedStorage")
local UpdateInventoryEvent = RS.UpdateInventory -- This event will tell the client to update their inventory.
function DF.AddItemToInventory(player, item)
local UserInventory = ProfileManager.Profiles[player].Data.Inventory -- you would just switch this up to wherever your table was if you were using DataStoreService
ProfileManager.Profiles[player].Data.Inventory[#UserInventory + 1] = item
UserInventory = ProfileManager.Profiles[player].Data.Inventory
UpdateInventoryEvent:FireClient(player, UserInventory)
return UserInventory
end
Now the client has access to a new inventory every time it is updated.
If you are wondering what it would look like from the client, then here you go:
I recommend in StarterPlayerScripts having ONE script that manages the updating the inventory.
local RS = game:GetService("ReplicatedStorage")
local UpdateInventoryEvent = RS.UpdateInventory
local currentPlayerInventory = {} -- This is the table that we will be updated every time for the client.
UpdateInventoryEvent.OnClientEvent:Connect(function(updatedInventory)
currentPlayerInventory = updatedInventory
end)
The reason why I recommend having only one local script that holds this information, is because you aren’t confused who has the latest information or what-not. We are making sure that this local script ALWAYS has up-to-date information on the player’s inventory.
So finally, how do we get this information anywhere on the client that needs it?
We just fire a bindable event every time the inventory is updated.
It would look something like this:
local RS = game:GetService("ReplicatedStorage")
local UpdateInventoryEvent = RS.UpdateInventory
local SendInventoryToClientScripts = RS.SendInventory
local currentPlayerInventory = {} -- This is the table that we will be updated every time for the client.
UpdateInventoryEvent.OnClientEvent:Connect(function(updatedInventory)
currentPlayerInventory = updatedInventory
SendInventoryToClientScripts:Fire(currentPlayerInventory)
end)
Now from any client, we could just do a
local Inventory = {}
RS.SendInventory.Event:Connect(function(newInventory)
inventory = newInventory
end)
Alright to wrap this up, the reason why I told you to use proxy’s and metatables originally because I was assuming that you had an inventory system that was being updated randomly, and __newindex would allow you to update your inventory to the clients each time.
But if we have one localized area where we update the inventory, we could still send that data instantaneously without requiring metatables.
Sorry for the long reply.