Global Leaderboard not updating player data when the player is not in-game

So I followed a tutorial and got a model and customised everything, but the it only updates a players stats when they are in-game. E.G: There is a hacker ingame that has a insane amount of “coins”. A admin sees this on the global leaderboard and resets their data using data:RemoveAsync() the hacker gets banned and their data gets reset. But my leaderboard script only updates when the player to update is playing the game.
Script:

wait(1)
-- [ SETTINGS ] --

local statsName = "emeralds" -- Your stats name
local maxItems = 50 -- Max number of items to be displayed on the leaderboard
local minValueDisplay = 30 -- Any numbers lower than this will be excluded
local maxValueDisplay = 10e15 -- (10 ^ 15) Any numbers higher than this will be excluded
local abbreviateValue = false -- The displayed number gets abbreviated to make it "human readable"
local updateEvery = 10 -- (in seconds) How often the leaderboard has to update
local headingColor = Color3.fromRGB(0, 169, 15) -- The background color of the heading

-- [ END SETTINGS ] --




-- Don't edit if you don't know what you're doing --

local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local DataStore = DataStoreService:GetOrderedDataStore("LeaderstatsData" .. statsName)
local Frame = script.Parent.Frame
local Contents = Frame.Contents
local Template = script.objTemplate

local COLORS = {
	Default = Color3.fromRGB(38, 50, 56),
	Gold = Color3.fromRGB(255, 215, 0),
	Silver = Color3.fromRGB(192, 192, 192),
	Bronze = Color3.fromRGB(205, 127, 50)
}
local ABBREVIATIONS = { "K", "M", "B", "T" }


local function toHumanReadableNumber(num)
	if num < 1000 then
		return tostring(num)
	end
	
	local digits = math.floor(math.log10(num)) + 1
	local index = math.min(#ABBREVIATIONS, math.floor((digits - 1) / 3))
	local front = num / math.pow(10, index * 3)
	
	return string.format("%i%s+", front, ABBREVIATIONS[index])
end

local function getItems()
	local data = DataStore:GetSortedAsync(false, maxItems, minValueDisplay, maxValueDisplay)
	local topPage = data:GetCurrentPage()

	Contents.Items.Nothing.Visible = #topPage == 0 and true or false

	for position, v in ipairs(topPage) do
		local userId = v.key
		local value = v.value
		local username = "[Not Available]"
		local color = COLORS.Default

		local success, err = pcall(function()
			username = Players:GetNameFromUserIdAsync(userId)
		end)

		if position == 1 then
			color = COLORS.Gold
		elseif position == 2 then
			color = COLORS.Silver
		elseif position == 3 then
			color = COLORS.Bronze
		end

		local item = Template:Clone()
		item.Name = username
		item.LayoutOrder = position
		item.Values.Number.TextColor3 = color
		item.Values.Number.Text = position
		item.Values.Username.Text = username
		item.Values.Value.Text = abbreviateValue and toHumanReadableNumber(value) or value
		item.Parent = Contents.Items
	end
end


script.Parent.Parent.Color = headingColor
Frame.Heading.ImageColor3 = headingColor
Frame.Heading.Bar.BackgroundColor3 = headingColor

while true do
	for _, player in pairs(Players:GetPlayers()) do
		local leaderstats = player:FindFirstChild("leaderstats")

		if not leaderstats then
			warn("Couldn't find leaderstats!")
			break
		end

		local statsValue = leaderstats:FindFirstChild(statsName)

		if not statsValue then
			warn("Couldn't find " .. statsName .. " in leaderstats!")
			break
		end

		pcall(function()
			DataStore:UpdateAsync(player.UserId, function()
				return tonumber(statsValue.Value)
			end)
		end)
	end

	for _, item in pairs(Contents.Items:GetChildren()) do
		if item:IsA("Frame") then
			item:Destroy()
		end
	end

	getItems()

	wait()
	Frame.Heading.Heading.Text = "THE VILLAGERS"
	Contents.GuideTopBar.Value.Text = statsName
	wait(updateEvery)
end
3 Likes

if theres only a few people on the leaderboard you can delete the hackers manually with any datastore edit plugin or with some command line snippet

if you want to do it in the script then use the ids of the players that you get from :GetStortedAsync to reconstruct their datstore key and then save their current data to the ordered data store

2 Likes

how in script would i do that?

1 Like

Storing data as a DataStoreService is not recommended. Try ProfileService, which is not official but is used by most people.

If you find the script which admins use to ban people, you can remove the data store entry automatically through there with DataStore:RemoveAsync(). This makes it so any hackers will just have their data removed from the leaderboard.

This can also be possible if you have some kind of external admin panel which uses the HTTP OpenCloud API.

Basically, as long as you can access the data store, it’s doable automatically.


ProfileService uses Roblox data stores. An OrderedDataStore is the best option here.

But isn’t OrderedDataStore also similar to GlobalDataStore? Wouldn’t ProfileService be more secure instead of using GlobalDataStore, as it has provisions in place to prevent data loss in some cases?

GlobalDataStore is a class which all data store variants inherit from.

Since this is a leaderboard, it only needs to mimic player data, i.e. if saving fails it’ll just be updated to the new value next save. We don’t really need to worry about data loss in the leaderboard for this reason, just using a standard SetAsync with a pcall should be fine.

If it was the actual player data, yes, we would need to be much more careful to ensure data saves properly, which is where things like session locking and data versioning come into play. ProfileService would be good for that, but bare in mind it mainly puts in place features which people don’t know how to/are not confident enough to put in themselves, so they use ProfileService which does it for them. Your data store system could be as secure if not more secure than ProfileService if you use the APIs and data stores right.

1 Like

I got it. Thanks for a information.

1 Like

I already use that to reset stats.

But do you use it for the leaderboard entries? RemoteAsync deletes the latest entry, so using OrderedDataStore:RemoveAsync() for your leaderboard entries should get rid of the ones you want to remove.

If you want to remove their entry whilst they are offline, you will need the data store entry key. This is the key you save data with in the OrderedDataStore, which should include their UserId as well. If you want their UserId, you can use Players:GetUserIdFromNameAsync() and remove it using that.

this is a slice of my reset script:

print("Reset Emeralds command requested and recieved.")
		print("Info: Moderator: "..player.Name..". Requested Emerald Reset on: "..value..".")
		local DataStoreService = game:GetService("DataStoreService")
		local leaderstatsData = DataStoreService:GetDataStore("LeaderstatsData")
		local foundPlayerID = game.Players:GetUserIdFromNameAsync(value)
		if foundPlayerID then
			
		print("Username has been identified as legitimate player. Found player ID: "..foundPlayerID)
			
		game.Players:BanAsync({
			UserIds = {foundPlayerID},
			ApplyToUniverse = true,
			Duration = 30,
			DisplayReason = "Your emeralds were reset. Your data is processsing. This should take around 30 seconds.",
			PrivateReason = "User has been emerald resetted by @"..player.Name,
			ExcludeAltAccounts = true
		})
		wait(1.3)
			leaderstatsData:RemoveAsync(tostring(foundPlayerID))

yep, you’re not removing the player’s data from the leaderboard, only the leaderstats data store. You can use GetOrderedDataStore for the leaderboard name and use RemoveAsync on that.

There seems to be a lot of network request functions here. Are these run in a pcall?

1 Like

What do you mean the leaderboard name? Like the name of the script or leaderboard part?

1 Like

You see how at the top of the leaderboard script, there’s a DataStoreService:GetOrderedDataStore() call? You need to remove data from that data store when you ban a player.

local leaderboard = DataStoreService:GetOrderedDataStore("LeaderstatsData_emeralds")
leaderboard:RemoveAsync(tostring(foundPlayerId))
1 Like