Help with stats

Does it save in the game itself, though? Because maybe then it is just a studio bug or something.

Make sure to publish the place before testing it.

I did, in game it still doesn’t save. Super weird

There might be something wrong with getting the data. In the saving, it works just fine.
Also in the data saving, remove the before, after, and data being saved print statements. They aren’t necessary anymore.


Go back up to where the data loads. This is all of that same code, just instead it has an extra print that tells you what the data is. Please show me what that says.

local Data

local success, errorMessage = pcall(function() --// A safety net for the script in case the call fails. This should be used with any API call (literally every built in function that has "Async" somewhere in it). If the API call fails, it will error and break the entire script. API calls can fail very easily, especially with player's that have slow internet or low-end devices. If it fails, it will only break inside of the pcall, and the rest of the script will run.
	Data = DataStore:GetAsync(Player.UserId) -- Get Data
end)

if success and Data ~= nil then --// Checks if the pcall was successful and if the data is not equal to nil. If it can receive a successful call, but the user has no data, then it will still run just fine, but it will error when you try to apply the nil data to the value/property of something
	print("Successfully loaded data for "..Player.Name)
	print("This is the data that was loaded:", Data) --// The line I added. Tell me what this prints out

	Minutes.Value = Data
else
	warn(errorMessage)
end

I also moved the line Minutes.Value = Data into that if statement. You should do the same and remove the same line a little farther down in that PlayerAdded event. It is better to have it in the if statement.


Here is what the output says, stll nothing saves

Well, load into the game again and tell me if it prints “This is the data being loaded: 0”
It should because it says that it loaded “5”, meaning it did save.

There is probably an issue with setting the value of the Minutes stat.


When I first load it says this

And here it is after


And reloaded back into game

yet my leaderstats say 0?
image

Okay, so now I know it is indeed saving and loading. There is most definitely an issue with setting the value.

I think with how you are using ProfileService and the basic DataStoreService together at the same time, both are interfering with each other. What I would do is probably remove ProfileService from your code entirely. Delete the module and start a clean, server script (inside of ServerScriptService.)

I can guide you on how to. However, it is getting pretty late for me. I’m going to go to bed, but as soon as I wake up and get through my morning routine, I will help you out. If anyone else offers help before then, feel free to take their help. I don’t mind if you do.

Sounds good ill remove everything tomorrow. Let me know what you can help and ill get on!

Hey hey! I’m ready whenever you can help me guide on how to transfer everything!

It shouldn’t be very hard to do. There are quite a few major changes that need to be made, though. If you still have that DataStore script, delete it and create a new one. Also get rid of the ProfileService module since you won’t need it anymore.

A mostly ideal DataStore script would look something like this
-- >>: Variables
local Players = game:GetService("Players")
local dataStoreService = game:GetService("DataStoreService")
local httpService = game:GetService("HttpService")

local dataStore = dataStoreService:GetDataStore("DataStore")

local defaultTable = {
	Cash = 0;
	Gems = 0;
	Wins = 0;
	Deaths = 0;
	
	EquippedWeapon = "ClassicSword";
	
	OwnedWeapons = {
		"ClassicSword";
		"TimeBomb";
		"RocketLauncher";
		"Slingshot";
		"Trowel";
	};
}

-- >>: Functions
local function PlayerAdded(player: Player)
	-- // Stats
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player
	
	local cash = Instance.new("IntValue")
	cash.Name = "Cash"
	cash.Value = defaultTable.Cash
	cash.Parent = leaderstats
	
	local gems = Instance.new("IntValue")
	gems.Name = "Gems"
	gems.Value = defaultTable.Gems
	gems.Parent = leaderstats
	
	local wins = Instance.new("IntValue")
	wins.Name = "Wins"
	wins.Value = defaultTable.Wins
	wins.Parent = leaderstats
	
	local deaths = Instance.new("IntValue")
	deaths.Name = "Deaths"
	deaths.Value = defaultTable.Deaths
	deaths.Parent = leaderstats
	
	local equippedWeapon = Instance.new("StringValue")
	deaths.Name = "EquippedWeapon"
	deaths.Value = defaultTable.EquippedWeapon
	deaths.Parent = player
	
	local ownedWeapons = Instance.new("Folder")
	ownedWeapons.Name = "OwnedWeapons"
	ownedWeapons.Parent = player
	
	--// Loading Data
	local encoded
	local dataTable
	
	local success, errorMessage = pcall(function()
		encoded = dataStore:GetAsync("dataTable-"..player.UserId)
	end)
	
	if success then
		if encoded ~= nil then
			dataTable = httpService:JSONDecode(encoded)
			
			if dataTable ~= nil then
				cash.Value = dataTable.Cash
				gems.Value = dataTable.Gems
				wins.Value = dataTable.Wins
				deaths.Value = dataTable.Deaths
				
				equippedWeapon.Value = dataTable.EquippedWeapon
				
				for i, v in pairs(dataTable.OwnedWeapons) do
					local stringValue = Instance.new("StringValue")
					stringValue.Name = v
					stringValue.Parent = ownedWeapons
				end
			end
		end
	else
		if errorMessage then
			warn(errorMessage, player.Name, player.UserId)
		elseif encoded == nil then
			warn("Data for "..player.Name.." is nil!", player.UserId)
		end
	end
end

local function PlayerRemoving(player: Player)
	local dataTable = {
		Cash = player.leaderstats.Cash.Value;
		Gems = player.leaderstats.Gems.Value;
		Wins = player.leaderstats.Wins.Value;
		Deaths = player.leaderstats.Deaths.Value;

		EquippedWeapon = player.EquippedWeapon.Value;

		OwnedWeapons = {};
	}
	
	for _, v in pairs(player.EquippedWeapons:GetChildren()) do
		table.insert(dataTable.OwnedWeapons, v.Name)
	end
	
	local encoded = httpService:JSONEncode(dataTable)
	
	local success, errorMessage = pcall(function()
		dataStore:SetAsync("dataTable-"..player.UserId, encoded)
	end)
end

-- >>: Events
Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(PlayerRemoving)

-- >>: Bind To Close
game:BindToClose(function()
	task.wait(60)
end)

This structure is also the same that I will guide you on creating.

Alright, just deleted the Profile service scripts and make a new fresh one with the code you provided

Do not. There are a couple bugs I noticed with it, as well as you should create a new structure in general. As well as it isn’t written with the same stats you had earlier.

I will be back in a few minutes after I fix the bugs with it.

Ahh I see, ill wait for you then! I’m sorry im not the best at coding I mostly got everything from youtube :joy:

1 Like

Here is an actually working version of what I originally wrote. This one still isn’t written to have the same stats and features as yours originally did, but this is just the general structure for a basic DataStore

-- >>: Variables
local Players = game:GetService("Players")
local dataStoreService = game:GetService("DataStoreService")
local httpService = game:GetService("HttpService")

local dataStore = dataStoreService:GetDataStore("DataStore")

local defaultTable = {
	Cash = 0;
	Gems = 0;
	Wins = 0;
	Deaths = 0;

	EquippedWeapon = "ClassicSword";

	OwnedWeapons = {
		"ClassicSword";
	};
} --// A default table that stores all of the default values for any stats in your game. This is used if the user has no data, or if the data fails to load/decode

-- >>: Functions
local function PlayerAdded(player: Player)
	-- // Stats
	local leaderstats = Instance.new("Folder") --// Creates a leaderstats folder
	leaderstats.Name = "leaderstats" --// Names it
	leaderstats.Parent = player --// Sets its parent

	local cash = Instance.new("IntValue") --// Creates a Cash stat
	cash.Name = "Cash" --// Names it
	cash.Value = defaultTable.Cash --// Sets its value
	cash.Parent = leaderstats --// Sets its parent

	local gems = Instance.new("IntValue") --// Creates a Gems stat
	gems.Name = "Gems" --// Names it
	gems.Value = defaultTable.Gems --// Sets its value
	gems.Parent = leaderstats --// Sets its parent

	local wins = Instance.new("IntValue") --// Creates a Wins stat
	wins.Name = "Wins" --// Names it
	wins.Value = defaultTable.Wins --// Sets its value
	wins.Parent = leaderstats --// Sets its parent

	local deaths = Instance.new("IntValue") --// Creates a Deaths stat
	deaths.Name = "Deaths" --// Names it
	deaths.Value = defaultTable.Deaths --// Sets its value
	deaths.Parent = leaderstats --// Sets its parent
	
	local equippedWeapon = Instance.new("StringValue") --// Creates an EquippedWeapon stat
	equippedWeapon.Name = "EquippedWeapon" --// Names it
	equippedWeapon.Value = defaultTable.EquippedWeapon --// Sets its value
	equippedWeapon.Parent = player --// Sets its parent

	local ownedWeapons = Instance.new("Folder") --// Creates an OwnedWeapons folder
	ownedWeapons.Name = "OwnedWeapons" --// Names it
	ownedWeapons.Parent = player --// Sets its parent

	--// Loading Data
	local encoded --// Creates a new, empty variable that will be set in the pcall
	local dataTable --// Creates a new, empty variable that will be set to the table that is decoded version of the JSONEncoded string. So instead of a string, it is an actual table

	local success, errorMessage = pcall(function() --// Creates a safety net in case the API call fails so the entire script won't break
		encoded = dataStore:GetAsync("dataTable-"..player.UserId) --// Gets the saved, encoded string from the player's DataStore
	end)

	if success then --// Only runs if it was successful
		if encoded ~= nil then --// Only runs if the "encoded" variable is not equal to nil. It will only be equal to nil if something goes wrong in the pcall
			dataTable = httpService:JSONDecode(encoded) --// Uses HttpService to decode the encoded version of the player's DataTable

			if dataTable ~= nil then --// Only runs if the dataTable is not equal to nil. It will only be equal to nil if there is an error while decoding the encoded table.
				print("Successfully loaded data table for "..player.Name, dataTable) --// Prints out that the data loading was a success, and will then proceed to set the stats with the values inside of the table
			else --// Only gets ran when the dataTable variable is equal to nil, which only happens when there is an error decoding the encoded table
				warn("Data table for "..player.Name.." failed to be decoded") --// Prints out that the data table could not be decoded
				dataTable = defaultTable --// Sets the dataTable variable to the default data table defined at the top of the script
			end
		else --// Only runs when the encoded variable is equal to nil. This will only happen when the player has no saved data
			warn("There is no saved data table for "..player.Name) --// Prints out that the player has no saved data
			dataTable = defaultTable --// Sets the dataTable variable to the default data table defined at the top of the script
		end
		
		cash.Value = dataTable.Cash --// Sets the value of "Cash" to the value in the data table
		gems.Value = dataTable.Gems --// Sets the value of "Gems" to the value in the data table
		wins.Value = dataTable.Wins --// Sets the value of "Wins" to the value in the data table
		deaths.Value = dataTable.Deaths --// Sets the value of "Deaths" to the value in the data table

		equippedWeapon.Value = dataTable.EquippedWeapon --// Sets the value of "EquippedWeapon" to the value in the data table

		for i, v in pairs(dataTable.OwnedWeapons) do --// Loops through every single item inside of the OwnedWeapons table in the data table, which will repeat the code below for every item in the table
			local stringValue = Instance.new("StringValue") --// Creates a new string value
			stringValue.Name = v --// Sets its name to the string in the OwnedWeapons table
			stringValue.Parent = ownedWeapons --// Sets its parent to the OwnedWeapons folder inside of the player
		end
	else --// Only runs when the pcall is not successful. Could be a wide range of many errors
		if errorMessage then --// If there is an error message, it will print it out. When a pcall is not successful, it will always have an error message anyway
			warn(errorMessage, player.Name, player.UserId) --// Prints out the error message, player name, and player UserId
		elseif encoded == nil then --// Only runs when the encoded value is equal to nil and the errorMessage is nil. This should never happen because there is always an error message when pcalls fail
			warn("Data for "..player.Name.." is nil!", player.UserId) --// Prints that the data for the player is nil
		end
	end
end

local function PlayerRemoving(player: Player)
	local success, errorMessage = pcall(function() --// Creates a new pcall. A safety net for if the API call fails so it won't break the entire script
		local dataTable = {
			Cash = player.leaderstats.Cash.Value;
			Gems = player.leaderstats.Gems.Value;
			Wins = player.leaderstats.Wins.Value;
			Deaths = player.leaderstats.Deaths.Value;

			EquippedWeapon = player.EquippedWeapon.Value;

			OwnedWeapons = {};
		} --// Creates a new data table that will be encoded and saved. This table includes all of the values

		for _, v in pairs(player.OwnedWeapons:GetChildren()) do --// Similarly to loading the data, it will make a loop that will repeat the code below for every item. The only difference is instead of creating a StringValue inside of the OwnedWeapons folder from every string in the table, it creates a string in the table for every StringValue in the OwnedWeapons folder
			table.insert(dataTable.OwnedWeapons, v.Name) --// Inserts a string with the same name for the StringValue it is currently on
		end
		
		local encoded = httpService:JSONEncode(dataTable) --// Using HttpService to encode the table, converting it into a string to be saved. You should always do this because eventually a table will get larger than four megabytes, and each DataStore can only hold four megabytes.
		
		--// Everything done above should be in the pcall as well, not just the SetAsync() call. If there isn't a value created inside of the player when they leave, or something is missing that is required in the data table, if it errors, it will break the entire script for every player, causing every player in the server to lose their data

		dataStore:SetAsync("dataTable-"..player.UserId, encoded) --// Saves 
	end)
	
	if success then --// Only runs when the pcall is successful
		print("Successfully saved data for "..player.Name, player.UserId) --// Prints that the data saving was a success
	else --// Only runs when the pcall fails. It could fail for a wide range of reasons
		warn(errorMessage) --// Prints the error message
	end
end

-- >>: Events
Players.PlayerAdded:Connect(PlayerAdded) --// Runs the PlayerAdded function when a player is joining the game
Players.PlayerRemoving:Connect(PlayerRemoving) --// Runs the PlayerRemoving function when a player is leaving the game

-- >>: Bind To Close
game:BindToClose(function() --// Only runs the code inside when the last player of the server is leaving. This is so the data has enough time to save, because if the server closes before it can fully save, it will cause data loss
	task.wait(60) --// Waits 60 seconds before the server closes when the last player leaves the game
end)

I wrote this just so you can get a general idea on what it should look like, and I added comments to clarify if you don’t know what something does.

Thank you I’ll take a look at it when I’m available. In the leaderstat script I had previously was there anything special that happened for the donations that I should be aware about? Like firing anything or stuff? Or is it just a leaderstat that displays how much I’ve raised / donated and saves it

It was the latter of the two. Only a leaderstat.