Issue connecting Player Data and Player GUI

Hi there,

I have a particular issue in my game by which player data (character skins) is able to be saved to the server, but this information has trouble being illustrated in a GUI (shop/inventory) in two noted situations. I will use a test user to illustrate this problem:

  1. When Player 2 enters the server, the datastore recalls that they had previously bought a skin
    (“ZT-B”), as indicated in their SkinInventory folder. However, when Player 2 opens their inventory, the skin is not there. On the other hand, they are able to find their saved skin in the shop (intended feature, but the skin shows up in one place as opposed to both places simultaneously). See images below:

image

  1. For Player 2, shop skins appear as intended before the round starts. However, when the round ends, they cannot find any skins in the shop or their inventory. Again, Player 2 has the values in the SkinInventory folder. Keep in mind that there are team-changing activities in the background…

I do not understand what is going on. I have been trying to work around this issue for many months, and it’s doing my head in. If you have any insight, please share! From what I understand, my datastore works sufficiently, but there’s a communication issue between player data and the related gui.

There are two scripts involved in this process - one local and the other is server-sided.

Shop (Local Script):

local Shop = script.Parent
local HUD = Shop.Parent.Parent
local Inventory = HUD.Inventory.Inventory
local InventoryItemTemplate = Inventory.InventoryGUI:WaitForChild("Template")
local InventoryScrollingFrame = Inventory:WaitForChild("ScrollingSkinLayout")

local Home = Shop:WaitForChild("Home")
local Skins = Shop:WaitForChild("Skins")

local HomeButton = Shop:WaitForChild("Home Button")
local SkinsButton = Shop:WaitForChild("Skins Button")
local KillEffects = Shop:WaitForChild("Kill Effects Button")
local SpecialisationsButton = Shop:WaitForChild("Malware Specialisations Button")

local ItemTemplate = script:WaitForChild("Template") -- Skin template/frame is inside the script
local UIChangeSFX = script["UI Change"]


-- Inventory
HUD:WaitForChild("InventoryToggle").MouseButton1Click:Connect(function()
	HUD:WaitForChild("Inventory").Inventory.Visible = not HUD:WaitForChild("Inventory").Inventory.Visible
	UIChangeSFX:Play()
end)

function AddToFrame(v) -- We want to bring in the name of the Character into each frame, which is unique to that frame alone
	local Frame = InventoryItemTemplate:Clone()
	Frame.Name = v.Name
	Frame.Title.Text = v.Name
	Frame.Parent = InventoryScrollingFrame

	Frame.Icon.Image = v.HumanoidRootPart.MALWAREGUI.MALWARE.Image
	
	if game.Players.LocalPlayer.SkinInventory:FindFirstChild(v.Name) then
		Frame.Cost.Text = "Owned"
	end

	if game.Players.LocalPlayer.EquippedSkin.Value == v.Name then
		Frame.Cost.Text = "Equipped"
	end
	
	Frame.Cost.MouseButton1Click:Connect(function()
	
		local Result = game.ReplicatedStorage.AddToInventory:InvokeServer(v.Name, "Skin")
		
		if Result == "Bought" then
			
			Frame.Cost.Text = "Equipped"	
			
			print(Result)

			for _, Object in pairs(InventoryScrollingFrame:GetChildren()) do
			if Object:IsA("Frame") and Object:FindFirstChild("Status") then
					if game.Players.LocalPlayer.SkinInventory:FindFirstChild(Object.Name) then
						Object.Cost.Text = "Owned"
					end
				end
			end

			Frame.Cost.Text = "Equipped"

		end
	end)
end


-- Shop
HUD:WaitForChild("ShopToggle").MouseButton1Click:Connect(function()
	HUD:WaitForChild("Shop").Shop.Visible = not HUD:WaitForChild("Shop").Shop.Visible
	UIChangeSFX:Play()
end)


HomeButton.MouseButton1Click:Connect(function()
	Skins.Visible = false
	UIChangeSFX:Play()
	Home.Visible = true
end)

SkinsButton.MouseButton1Click:Connect(function()
	Home.Visible = false
	UIChangeSFX:Play()
	Skins.Visible = true
end)

local SkinsData = game.ReplicatedStorage.Skins:GetChildren() -- Returns a table of all the skins in the game 


function CreateFrame(Name, Cost, Object, Parent) -- Finding image within object itself to display
	local Frame = ItemTemplate:Clone()
	Frame.Name = Name
	Frame.Title.Text = Name
	Frame.Cost.Text = Cost
	Frame.Icon.Image = Object.HumanoidRootPart.MALWAREGUI.MALWARE.Image
	
	Frame.Parent = Parent
	
	return Frame
end


function AddSkins(Data) -- Loops through all skins and creates individual cells for them
	for i, v in pairs(Data) do
		local Frame = CreateFrame(v.Name, v.Cost.Value, v, Skins.ScrollingSkinLayout.Folder) -- Adding criteria for the UIGridLayout
		
		if game.Players.LocalPlayer.SkinInventory:FindFirstChild(v.Name) then
			Frame.Cost.Text = "Owned"
		end
		
		if game.Players.LocalPlayer.EquippedSkin.Value == v.Name then
			Frame.Cost.Text = "Equipped"
		end
		
		Frame.Cost.MouseButton1Click:Connect(function()
			local Result = game.ReplicatedStorage.BuyItem:InvokeServer(v.Name, "Skin")
			print(Result)
			
			if Result == "Bought" then -- if Purchase was a Success 
				Frame.Cost.Text = "Owned"	
				
				AddToFrame(v)
				
			elseif Result == "Equipped" then -- Skin is Equipped
				print(Result)
				
				for _, Object in pairs(Skins.ScrollingSkinLayout.Folder:GetChildren()) do
					if Object:IsA("Frame") and Object:FindFirstChild("Cost") then
						if game.Players.LocalPlayer.SkinInventory:FindFirstChild(Object.Name) then
							Object.Cost.Text = "Owned"
						end
					end
				end
				Frame.Cost.Text = "Equipped"		
			end
		end)
	end
end

game.ReplicatedStorage.SendData.OnClientEvent:Connect(function()
	AddSkins(SkinsData)
end)

Events (Server Script)

-- Buy from Shop
game.ReplicatedStorage.BuyItem.OnServerInvoke = function(Player, ItemName, ItemType)
	local Item
	local InInventory
	
	if ItemType == "Skin" then
		Item = game.ReplicatedStorage.Skins:FindFirstChild(ItemName)
		
		if Player.SkinInventory:FindFirstChild(ItemName) then
			InInventory = true
			print("placed into inventory")
		end
	end
	if Item then
		if Item:FindFirstChild("Cost") then
			if not InInventory then
				
				if Item.Cost.Value <= Player.Credits.Value then
					print("this can be bought")
					Player.Credits.Value = Player.Credits.Value - Item.Cost.Value
					
					local StringValue = Instance.new("StringValue")
					StringValue.Name = Item.Name
					
					if ItemType == "Skin" then
						StringValue.Parent = Player.SkinInventory
					
					end
					return "Bought"
				else
					return "Failed"
				end
			else
				-- Item is already owned
				print("you already own this skin")
				if ItemType == "Skin" then
					Player.EquippedSkin.Value = ItemName
				end
				return "Equipped"
			end
		end
	end
end

-- Make Inventory item equippable
game.ReplicatedStorage.AddToInventory.OnServerInvoke = function(Player, ItemName, ItemType)
	local Item
	local InInventory

	if ItemType == "Skin" then
		Item = game.ReplicatedStorage.Skins:FindFirstChild(ItemName)

		if Player.SkinInventory:FindFirstChild(ItemName) then
			InInventory = true
		end
	end
	if Item then
		if Item:FindFirstChild("Cost") then
			if InInventory then
				if ItemType == "Skin" then
					Player.EquippedSkin.Value = ItemName
				end

				return "bought"

			else
				return "failed"
			end
		else
			-- Equip it!
			print("you already own this item")
			if ItemType == "Skin" then
				Player.EquippedSkin.Value = ItemName
			end
			return "equipped"
		end
	end
end

Just bumping my post :slight_smile:

If there’s anything missing for extra context, I’m more than happy to share what’s going on.

Are you giving the client enough time to load before fetching current data i.e. ui is loaded before fetching inventory update(s)

is the GUI property “ResetOnDeath” (or something like that) set to true?

1 Like

Thank you for your reply.

Regarding your first question, there is no such thing implemented in my game. Do you suggest adding a wait() line or something?

For your second question, here is how my ScreenGUIs are organised…
image

When I disable ResetOnDeath (i.e., set to false) for LobbyHUD + Inventory, Shop and Spectate GUIs, everything works (I can see shop items between each round). However, when the player dies, they are no longer able to prompt the Inventory, Shop and Spectate ScreenGUIs as they click the toggle buttons.

it varies depending on code bases (the solution also varies based on code base), I am only stating the above in reference to my own, I do the following:

letting the server know when the client has fully loaded up (or enough where it can now listen for data retrieval events)

I disable resetguiondeath due to avoiding unnecessary work, why do I need to repopulate the UI? As far as your issue there, its likely something to do with your code, maybe you disable the toggle at the start of the match and relied up resetting of the gui itself rather than unsetting the disable in code.

This post is probably gonna be long, and I’m sorry for that, just that there is a lot at play, and I would rather ask/provide too much, then cutting corners and not understanding me.

For starters:

You can Use the function on the client Humanoid.Died and disable that, or add A fix? I don’t understand if you mean “The toggle buttons don’t work” or… “The toggle buttons are in the way and need to be disabled on death”. Some more information can help me there.

Yes! I don’t know what the code is behind game.ReplicatedStorage.SendData.OnClientEvent:Connect(function()
If it is a one time deal like on player join (or if it can be changed to make this work). I would change it to a remote function. So what would happen:

Client would send a request for data once loaded :arrow_right: Server Gives Data :arrow_right:Uses return, to send it back to client to use

Here is some recommend loading methods I use to load client side scripts in general:

repeat task.wait() until game:IsLoaded()
--Or
repeat task.wait() until game.Players:FindFirstChild(game.Players.LocalPlayer.Name) and game.Workspace:FindFirstChild(game.Players.LocalPlayer.Name)

--Or, for a more complex loading process:
local contentservice = game:GetService("ContentProvider")
repeat task.wait() until game.Players.LocalPlayer.PlayerGui:FindFirstChildWhichIsA("ScreenGui")
local ListOfGuiImages = {}


for i, EachObject in game.Players.LocalPlayer.PlayerGui:GetDescendants() do
	if EachObject:IsA("ImageLabel") or EachObject:IsA("ImageButton") then
		table.insert(ListOfGuiImages, EachObject.Image)
	end
end


contentservice:PreloadAsync({ListOfGuiImages})

Lastly (Issue 2 seems to be solved-ish, but not this, at least there hasn’t been a mention of it), Your scripts are long and I’m having an issue. Can you point out, what function, you are suppose to add them to the inventory frame…? I thought it was this:

But I don’t see a Frame.Visible = true which I would expect. So some help pointing out where in your script, each item is suppose to get added and be visible will be nice :slight_smile:

Hope this all helps!

1 Like

Sorry for the confusion. When I say “toggle”, I mean the player manually clicking a series of image buttons. These ones, to be exact:

image

When the player clicks any of those buttons (when the ResetOnSpawn property for LobbyHUD is disabled) when they respawn after being killed in the previous round, the request to open the related GUI is ignored somehow and the related ScreenGUIs won’t open. Do note that I have sfx that play whenever these buttons are clicked, and they do play in this case, so there’s an underlying issue. I have ResetOnSpawn enabled because it gets me somewhere (related ScreenGUIs open as opposed to permanently hung).

I missed that as I was copying my script. The client event is received in the Events server script during the process where data is recalled upon player join. I was following a tutorial and this code is ~8 months old, but I can assume that function was to recall the player’s data and update the shop according to this data (e.g., what skins were previously bought/not bought). It is currently a RemoteEvent.

Here is the full process of data saving for extra context:

local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("DataStore") -- Do not change name of this DataStore

game.Players.PlayerAdded:Connect(function(Player)
	
	-- Currency System
	local Credits = Instance.new("IntValue", Player) -- Inserts values into the player object
	Credits.Name = "Credits"
	Credits.Value = 0
	
	local SkinInventory = Instance.new("Folder", Player) -- Characters owned
	SkinInventory.Name = "SkinInventory"
	
	local EquippedSkin = Instance.new("StringValue", Player) -- Equipped character based on what was equipped before the player left
	EquippedSkin.Name = "EquippedSkin"
	
	local Data
	
	local Sucess, ErrorMessage = pcall(function()
		Data = DataStore:GetAsync(Player.UserId)
	end)
	
	if Data ~= nil then -- Load Credits, Skins, EquippedSkins
		
		if Data.EquippedSkin then
			EquippedSkin.Value = Data.EquippedSkin
			print("loaded equipped skin from last save")
		end
		
		if Data.Credits then
			Credits.Value = Data.Credits
			print("credits loaded")
		end
		
		if Data.Skins then
			for i, v in pairs(Data.Skins) do
				local StringValue = Instance.new("StringValue")
				StringValue.Name = v
				StringValue.Parent = SkinInventory
				print("inventory skins loaded")
			end
		end
	end
	
	game.ReplicatedStorage.SendData:FireClient(Player, Data) -- **Here is what you're looking for** 
	print("data sent to the player")
	
	

-- The following code block is included because it contains a valuable term, "Player", that is otherwise not defined here... No idea if it makes a difference to data saving though 

--Events that destroys Agent and MALWARE stringvalue tag when round ends, Agents die/leave, or when MALWARE leaves 
	Player.CharacterAdded:Connect(function(Character)
		Character.Humanoid.Died:Connect(function()
			local Teams = game:GetService("Teams")
			if Player:FindFirstChild("Agent") then
				Player.Agent:Destroy()
				Player:FindFirstChild("Playing"):Destroy() 
				local StringValue = Instance.new("StringValue")
				StringValue.Name = "DiedLastRound"
				StringValue.Parent = Player
				Player.Team = Teams["Lobby"]
			elseif Player:FindFirstChild("MALWARE") then
				Player.MALWARE:Destroy()
				Player:FindFirstChild("Playing"):Destroy()
				local StringValue = Instance.new("StringValue")
				StringValue.Name = "DiedLastRound"
				StringValue.Parent = Player
				Player.Team = Teams["Lobby"]
			end
		end)
	end)
end)

AddToFrame() serves as a method to bring a bought skin to the player’s inventory. When a player buys a skin from the shop, the skin is immediately presented in the player’s inventory. It is only when the round ends or they rejoin that the bought skin does not appear. Perhaps it’s to do with some confusion between AddSkins() (shop skin layout) and AddToFrame (add bought skin to the inventory). Further, the player can opt to equip skins from the shop and inventory, so that might be an unforeseen problem.

Here is a short video to show that the inventory frame is visible by default when a player buys a skin:

I’ve tried the series of coding you provided, and it seems to prompt me with two different outcomes.

The first line repeat task.wait() until game:IsLoaded() does nothing. If it’s any help, I placed it within my local script for my shop system.

The third option you provided regarding ContentProvider creates the following error in the output:
“Unable to cast Array to Content”. Keep in mind I believed it was optimal to create an independent local script to execute this function. Do you recommend placing it within the shop’s local script instead?

Just had a thought: would using ReplicatedFirst be a good solution to this problem of loading data/GUIs?

No. ReplicatedFirst is meant if you have your own custom loading screen. It otherwords, it halts everything around it, to load what is in there, and it should really only be used for a real loading screen.

Yes. As I had mentioned, you need the client to wait, for loading. Executing this function in a different local script ruins… the whole purpose… Unless you wanted to make a loading screen, and once done loading it executes an event to tell all scripts it’s done loading (something I do in my game). To fix the error you are having change the line: contentservice:PreloadAsync({ListOfGuiImages}) to contentservice:PreloadAsync(ListOfGuiImages)

I see this. And this is for all the skins. What happens if you add a AddToFrame(v) here:

for i, v in pairs(Data) do
AddToFrame(v)
		local Frame = CreateFrame(v.Name, v.Cost.Value, v, Skins.ScrollingSkinLayout.Folder) -- Adding criteria for the UIGridLayout

Or does the whole game just break down and I’m misunderstanding something

Also did you resetOnSpawn == false on all Screen Gui objects (inventory, Shop and spectate) and still it doesn’t work for your issue of the buttons?

image

1 Like

Your first point actually solved the issue regarding bought skins in the player’s inventory not showing up. Thanks for your help! :happy2:

Secondly…

I’ve just performed a test, and there’s two outcomes:

Disabling ResetOnSpawn for LobbyHud, Inventory, Shop and Spectate Test

  • Test 1: All Disabled, Standard Reset via Roblox Menu input (game not started): Success opening all of the GUIs after the round ends by clicking on each of the three buttons.

  • Test 2: All Disabled, Player Death during Round (in-game): Unsuccessful in opening all of the GUIs after the round ends.

Seems there’s an issue with the round system and team-changing properties. When players change teams via server input, their related GUIs (e.g., LobbyHUD, AgentHUD, MALWAREHUD) are enabled/disabled. Not sure if that is the issue, but here is the script anyway if it is:

LocalScript:

local Teams = game:GetService("Teams")
local Players = game:GetService("Players")
local Player = game.Players.LocalPlayer
local PlayerGUI = Player:WaitForChild('PlayerGui')

local LobbyHUD = Player.PlayerGui:FindFirstChild("LobbyHUD")
local AgentHUD = Player.PlayerGui:FindFirstChild("AgentHUD")
local MALWAREHUD = Player.PlayerGui:FindFirstChild("MALWAREHUD")
local GameStatus = Player.PlayerGui:FindFirstChild("GameStatus")

function TeamChange(AssignedTeam)
	if AssignedTeam.Name == "Agents" then
		LobbyHUD.Enabled = false
		LobbyHUD.Shop.Enabled = false
		LobbyHUD.Inventory.Enabled = false
		LobbyHUD.Spectate.Enabled = false
		wait(14)
		AgentHUD.Enabled = true
		
	elseif AssignedTeam.Name == "MALWARE" then
		LobbyHUD.Enabled = false
		wait(14)
		MALWAREHUD.Enabled = true
	else
		MALWAREHUD.Enabled = false
		AgentHUD.Enabled = false
		LobbyHUD.Enabled = true
	end
end

TeamChange(Player.Team)
Player:GetPropertyChangedSignal("TeamColor"):Connect(function()
	TeamChange(Player.Team)
end)

That is because these lines are always false, and you don’t mention them anywhere else, to make them true. I don’t know how your game works entirely. But I think this should work to you as intended by doing this:

elseif AssignedTeam.Name == "MALWARE" then
		LobbyHUD.Enabled = false
		wait(14)
		MALWAREHUD.Enabled = true
	else
		MALWAREHUD.Enabled = false
		AgentHUD.Enabled = false
		LobbyHUD.Enabled = true
LobbyHUD.Shop.Enabled = true
		LobbyHUD.Inventory.Enabled = true
		LobbyHUD.Spectate.Enabled = true
	end
end

If this doesn’t work as intended because your game works differently, whenever the game ends just add

LobbyHUD.Shop.Enabled = true
		LobbyHUD.Inventory.Enabled = true
		LobbyHUD.Spectate.Enabled = true

Those lines of code in the correct spot. :slight_smile:

1 Like

Yep, the first script works. Thank you!

I just did a test, and it seems that the inventory-shop-datastore script has an interesting unforeseen problem: skins that are not bought appear in the player’s inventory screen! Do note that the stringvalues for each of the skins do not appear in the SkinInventory folder within the player object.

Here are some images to illustrate this problem:


An exception to this problem is the “Kraken” skin, which is owned by the player (according to the shop), but has the same “…” status in relation to the skins that are not bought. Odd.

Nevertheless, this would have to do with the AddtoFrame(v) function that was added to the Shop block (AddSkins(Data)).

I do appreciate your continued support!

Oops, My bad. Sorry for all the bugs. Remove the last edit I did and instead do this:

for i, v in pairs(Data) do
		local Frame = CreateFrame(v.Name, v.Cost.Value, v, Skins.ScrollingSkinLayout.Folder) -- Adding criteria for the UIGridLayout
		
		if game.Players.LocalPlayer.SkinInventory:FindFirstChild(v.Name) then
			Frame.Cost.Text = "Owned" 
            AddtoFrame(v)
		end

The Kraken error tells me a lot. Your not giving the client enough time to load. Meaning were gonna need to change the scripts a little bit.

Instead of

game.ReplicatedStorage.SendData.OnClientEvent:Connect(function()
	AddSkins(SkinsData)
end)

Try this:

Shop Local script:

Change the SendData Remove Event Into A remote function.

--Use a loading method here,  or higher above the script. I will just use a simple one
repeat task.wait() until game.Players:FindFirstChild(game.Players.LocalPlayer.Name) and game.Workspace:FindFirstChild(game.Players.LocalPlayer.Name)
local PlayersData = game.ReplicatedStorage.SendData:InvokeServer(game.Players.LocalPlayer)
repeat task.wait() until PlayersData ~= nil
if PlayersData ~= "No Data" then
	AddSkins(SkinsData)
end

Data Saving:

local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("DataStore")

game.ReplicatedStorage.SendData.OnServerInvoke = function(Player)

	-- Currency System
	local Credits = Instance.new("IntValue", Player) -- Inserts values into the player object
	Credits.Name = "Credits"
	Credits.Value = 0

	local SkinInventory = Instance.new("Folder", Player) -- Characters owned
	SkinInventory.Name = "SkinInventory"

	local EquippedSkin = Instance.new("StringValue", Player) -- Equipped character based on what was equipped before the player left
	EquippedSkin.Name = "EquippedSkin"

	local Data

	local Sucess, ErrorMessage = pcall(function()
		Data = DataStore:GetAsync(Player.UserId)
	end)

	if Data ~= nil then -- Load Credits, Skins, EquippedSkins

		if Data.EquippedSkin then
			EquippedSkin.Value = Data.EquippedSkin
			print("loaded equipped skin from last save")
		end

		if Data.Credits then
			Credits.Value = Data.Credits
			print("credits loaded")
		end

		if Data.Skins then
			for i, v in pairs(Data.Skins) do
				local StringValue = Instance.new("StringValue")
				StringValue.Name = v
				StringValue.Parent = SkinInventory
				print("inventory skins loaded")
			end
              
		end
	else
		return "No Data"
	end

	  return Data 
	
end --These ends were for a test. Remove as needed, and add the 'return Data' at the very end though!

This should work. Although their might be some Errors because I did this in a little rush.

Hmm. It seems that the data-saving script does not work. Here is what is happening to the player:


Might be the number of ends confusing the script, but the following attached script was produced without any errors.

Data-Saving script:

local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("DataStore")

game.Players.PlayerAdded:Connect(function(Player)
	
	-- Currency System
	local Credits = Instance.new("IntValue", Player) -- Inserts values into the player object
	Credits.Name = "Credits"
	Credits.Value = 0
	
	-- SkinInventory System
	local SkinInventory = Instance.new("Folder", Player) -- Characters owned
	SkinInventory.Name = "SkinInventory"
	
	-- EquippedSkin System
	local EquippedSkin = Instance.new("StringValue", Player) -- Equipped character based on what was equipped before the player left
	EquippedSkin.Name = "EquippedSkin"
	
	local Data

	local Sucess, ErrorMessage = pcall(function()
		Data = DataStore:GetAsync(Player.UserId)
	end)

	if Data ~= nil then -- Load Credits, Skins, EquippedSkins

		if Data.EquippedSkin then
			EquippedSkin.Value = Data.EquippedSkin
			print("loaded equipped skin from last save")
		end

		if Data.Credits then
			Credits.Value = Data.Credits
			print("credits loaded")
		end

		if Data.Skins then
			for i, v in pairs(Data.Skins) do
				local StringValue = Instance.new("StringValue")
				StringValue.Name = v
				StringValue.Parent = SkinInventory
				print("inventory skins loaded")
			end         
		else
			return "No Data"
		end
		return Data 
	end
	
	game.ReplicatedStorage.SendData:InvokeClient(Player, Data) -- Tried with InvokeClient and OnServerInvoke... not very familiar with remotefunctions 
	print("data sent to the player")

-- Rest of code

There is a lot of my Script that was not moved into your script. So I shall show you 2 methods, but please make sure whatever method you pick, your copy them as I have given because it won’t work if you don’t have it, unless you know what it doing and change it to what you want.

Method 1: Keep the Player Added function (Personal Recommendation)
Return back to the old data store and remove all the edits that I have shown you (still keeping the Remote function, instead of the remote event you had before). But do remove the line that does mention the remote event, and add the little thing I did here:

local DataStoreService = game:GetService("DataStoreService")
local DataStore = DataStoreService:GetDataStore("DataStore") -- Do not change name of this DataStore

game.Players.PlayerAdded:Connect(function(Player)
	
	-- Currency System
	local Credits = Instance.new("IntValue", Player) -- Inserts values into the player object
	Credits.Name = "Credits"
	Credits.Value = 0
	
	local SkinInventory = Instance.new("Folder", Player) -- Characters owned
	SkinInventory.Name = "SkinInventory"
	
	local EquippedSkin = Instance.new("StringValue", Player) -- Equipped character based on what was equipped before the player left
	EquippedSkin.Name = "EquippedSkin"
	
	local Data
	
	local Sucess, ErrorMessage = pcall(function()
		Data = DataStore:GetAsync(Player.UserId)
	end)
	
	if Data ~= nil then -- Load Credits, Skins, EquippedSkins
		
		if Data.EquippedSkin then
			EquippedSkin.Value = Data.EquippedSkin
			print("loaded equipped skin from last save")
		end
		
		if Data.Credits then
			Credits.Value = Data.Credits
			print("credits loaded")
		end
		
		if Data.Skins then
			for i, v in pairs(Data.Skins) do
				local StringValue = Instance.new("StringValue")
				StringValue.Name = v
				StringValue.Parent = SkinInventory
				print("inventory skins loaded")
			end
		end
	end
	
	--game.ReplicatedStorage.SendData:FireClient(Player, Data) -- We are removing this 
	--print("data sent to the player")
	
	

-- The following code block is included because it contains a valuable term, "Player", that is otherwise not defined here... No idea if it makes a difference to data saving though 

--Events that destroys Agent and MALWARE stringvalue tag when round ends, Agents die/leave, or when MALWARE leaves 
	Player.CharacterAdded:Connect(function(Character)
		Character.Humanoid.Died:Connect(function()
			local Teams = game:GetService("Teams")
			if Player:FindFirstChild("Agent") then
				Player.Agent:Destroy()
				Player:FindFirstChild("Playing"):Destroy() 
				local StringValue = Instance.new("StringValue")
				StringValue.Name = "DiedLastRound"
				StringValue.Parent = Player
				Player.Team = Teams["Lobby"]
			elseif Player:FindFirstChild("MALWARE") then
				Player.MALWARE:Destroy()
				Player:FindFirstChild("Playing"):Destroy()
				local StringValue = Instance.new("StringValue")
				StringValue.Name = "DiedLastRound"
				StringValue.Parent = Player
				Player.Team = Teams["Lobby"]
			end
		end)
	end)
end)

game.ReplicatedStorage.SendData.OnServerInvoke = function(Player)
	local Data

	local Sucess, ErrorMessage = pcall(function()
		Data = DataStore:GetAsync(Player.UserId)
	end)

	if Data ~= nil then
   return Data
else
   return "No Data"
end
end

Method 2, you use the method above, and double check what you have is correct:

You don’t have everything down in it. And missing a lot. Like, It should be game.ReplicatedStorage.SendData.OnServerInvoke = function(Player) instead of game.Players.PlayerAdded:Connect(function(Player).

You don’t need this on the server:

game.ReplicatedStorage.SendData:InvokeClient(Player, Data) -- Tried with InvokeClient and OnServerInvoke... not very familiar with remotefunctions

And the reason we don’t need this, is because return, gives the information back to the client. So client asks server, server returns data table, client gets this data table with the return function.

Lastly the rest of the code should go before return “No Data”. Return data should be the last thing (beside the ends) in that function. Although method 1 might be easier for you

BOTH Methods need this in their client!!!

--Use a loading method here,  or higher above the script. I will just use a simple one
repeat task.wait() until game.Players:FindFirstChild(game.Players.LocalPlayer.Name) and game.Workspace:FindFirstChild(game.Players.LocalPlayer.Name)
local PlayersData = game.ReplicatedStorage.SendData:InvokeServer(game.Players.LocalPlayer)
repeat task.wait() until PlayersData ~= nil
if PlayersData ~= "No Data" then
	AddSkins(SkinsData)
end

Sorry, I know that this is a lot, but by the time you have these changes, loading shouldn’t be an issue, so hey. I recommend method 1 now that I think about it, and might be easier to add.

1 Like

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