How to make a simple global leaderboard

Just change the initial scope, and use the user id as the key. You only need to change the 7th line of code, you don’t need to change anything else.

Meaning:

local dataStore = dataStoreService:GetOrderedDataStore("Leaderboard")
--Get the data store with key "Leaderboard"

To:

local dataStore = dataStoreService:GetOrderedDataStore('Leaderboard' .. DailyKeyGoesHere)
3 Likes

Does it works with screen guis too?

Yes, but you have to send new data to the client using remote events. The main reason I used surface guis was because I wanted to demonstrate leaderboards and make it as easy as possible to understand.

1 Like

Could you kindly go over daily in a bit more depth, I’m kind of struggling with it. I don’t quite understand the if condition (where you’re resetting the value) and am struggling to structure it in a script. Are you storing another variable called lastTimeDataStored?

Here is what my leaderboard DataStore script looks like, I don’t know how I would implement the required if conditions to make sure the Daily etc. are updating correctly.

local ProfileCache = require(game.ServerScriptService.ProfileCacher)

local dateTable_1 = os.date("*t", os.time())
local key_1 = "Y:"..dateTable_1["year"].." M:"..dateTable_1["month"].." D:"..dateTable_1["day"]
--Unique key for each day

local dateTable_2 = os.date("*t", os.time())
local key_2 = "Y:"..dateTable_2["year"].." M:"..dateTable_2["month"]
--Unique key for each month

local dateTable_3 = os.date("*t", os.time())
local key_3 = "Y:"..dateTable_3["year"]
--Unique key for each year

local winsDataStoreAllTime = game:GetService("DataStoreService"):GetDataStore("Test")
local winsDataStoreDaily = game:GetService("DataStoreService"):GetDataStore("Test" .. key_1)
local winsDataStoreMonthly = game:GetService("DataStoreService"):GetDataStore("Test" .. key_2)
local winsDataStoreYearly = game:GetService("DataStoreService"):GetDataStore("Test" .. key_3)

game:GetService("Players").PlayerAdded:Connect(function(player)
	if player then
		wait(10)
		
		local playerProfile = ProfileCache[player]
		local playerKey = "Player_" .. player.UserId
		
		while true do
			-- Autosave Wins data from ProfileService to leaderboard DataStore every 30 seconds
			
			local success, err = pcall(function()
				winsDataStoreDaily:SetAsync(playerKey, playerProfile.Data.Wins)
				print('Saved Value: ' .. tostring(winsDataStoreAllTime:GetAsync(playerKey)))
			end)
			
			local success, err = pcall(function()
				winsDataStoreDaily:SetAsync(playerKey, playerProfile.Data.Wins)
				print('Saved Value: ' .. tostring(winsDataStoreDaily:GetAsync(playerKey)))
			end)
			
			local success, err = pcall(function()
				winsDataStoreMonthly:SetAsync(playerKey, playerProfile.Data.Wins)
				print('Saved Value: ' .. tostring(winsDataStoreDaily:GetAsync(playerKey)))
			end)
			
			local success, err = pcall(function()
				winsDataStoreYearly:SetAsync(playerKey, playerProfile.Data.Wins)
				print('Saved Value: ' .. tostring(winsDataStoreDaily:GetAsync(playerKey)))
			end)
			
			wait(30)
		end
		
	end
end)

The method you use only ensures that the data loaded has been written on the same day, month, or year.

Say you have a value coins. The player gains 100 coins, and that value is written to the daily leaderboard. However, when the leaderboard resets the next day, and the player gains 100 coins, and they now have 200 coins, if you write 200 coins to the daily leaderboard, you no longer have a daily leaderboard.

There’s two solutions:

  • Easy: Update the daily value when it changes, so you only get the additional added and not the total value (for example, +100 instead of 200)
  • Complicated: Store a “daily value” with the player’s data, and add to it when you increment their coins or whatever value. Also save the day timestamp key which you can check, and if it doesn’t match the current one, reset the daily value.

TL;DR: It’s more complicated if you need to reset the value to only track a certain period of time, and the leaderboard won’t do it by itself.

I also explained this pretty well in an earlier reply, if you didn’t see it: How to make a simple global leaderboard - #76 by ThatTimothy

1 Like

Will this be okay if I have say 4 - 5 leaderboards which are copy and paste? The reason I ask, is because I would be writing to datastores at the same time. Couldn’t this cause dataloss?

As you can read in the FAQ section, you just change the scope to prevent this issue. For more information, see the FAQ.

(It’s a good idea to read the FAQ of a post if it has one before asking a question)

1 Like

Thanks for making this! If I were you I would go into more detail on the first post on how you can make daily leaderboards and stuff. I, myself, found this confusing at first until I read through all 100 posts. It would just help save time if this went into more detail. I could give an example of what you should say if wanted.

What do you mean? There’s nothing in the FAQ about that??

My mistake, it’s in the tutorial not the FAQ. Thought you were referring to daily leaderboard or something of that sort, not just a different leaderboard.

Edit: I have updates the FAQ with a section on making different leaderboards.

1 Like

It appears that the maxValue variable does not have an effect, I’m trying to make it really high but no matter it, it looks like its capped at 9.22e+18.

Here I’m printing the leaderstat value of coins, the maxValue in your script and the coins value being passed in your script upon being updated (same as leaderstats). Why is it only going to 9.22Qn?

image

When I print the values of coins in the loop that iterates through the data, it returns 9.22e+18 so it seems like the problem is the :GetSortedAsync()? Is there a cap to it? I’ve got it currently set at 10e65 but it doesnt matter what I put it as.

EDIT: Nvm it looks like maxValue is working, but I’m confused as to why it keep resorting to 9.22e+18 in the loop through the data?

local smallestFirst = false--false = 2 before 1, true = 1 before 2
local numberToShow = 100--Any number between 1-100, how many will be shown
local minValue = 1--Any numbers lower than this will be excluded
local maxValue = 10e65--(10^30), any numbers higher than this will be excluded
print('MaxValue: ' .. maxValue)
local pages = dataStore:GetSortedAsync(smallestFirst, numberToShow, minValue, maxValue)
--Get data
local top = pages:GetCurrentPage()--Get the first page
local data = {}--Store new data
for a,b in ipairs(top) do--Loop through data
	local userid = b.key--User id
	local coins = b.value--Coins
	print(coins)
	local username = "[Failed To Load]"--If it fails, we let them know
	local s,e = pcall(function()
		username = game.Players:GetNameFromUserIdAsync(userid)--Get username
	end)
	if not s then--Something went wrong
		warn("Error getting name for "..userid..". Error: "..e)
	end
	local image = game.Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size150x150)
	--Make a image of them
	table.insert(data,{username,coins,image})--Put new data in new table
end

This is the loop I’m talking about, b.value prints 9.22e+18

Forgot about something called data limits lol, what could I do to bypass :GetStoredAsync() 64 bit data limit?

If your game needs to store high value numbers, you can always store it’s power to the 10. For example, 100 would be stored as 2, 10,000 as 4. Since you can’t store decimals, you could also multiply by a precision factor such as 1000 to allow for 3 points of decimal precision. If you need an example look above or if that isn’t enough I can provide a code snippet.

Thank you for the prompt response. If you’d be so kind to provide a snippet. I’m not sure how I would apply this.

I’m trying to abbreviate the number in here:

dataStore:UpdateAsync(plr.UserId,function(oldVal)
	--Set new value
	return Format(tonumber(w), 2) -- K M T Q etc..
end)

However when I try to print coins in the loop through data it doesn’t print. Does it not like strings?

How would I treat numbers like 1e+22 or whatever

Strings cannot be ordered (at least not in the implementation), and so number values are required for OrderedDatastores. To “encode” numbers so that large ones can fit, you can use the following code:

local base = 10
local precision = 10000

local function encode(number)
	return math.floor(math.pow(number, 1 / base) * precision)
end

local function decode(encoded)
	return math.pow(encoded / precision, base)
end

local function test(number)
	local encoded = encode(number)
	local decoded = decode(encoded)
	print(string.format('Original: %f Encoded: %f Decoded: %f', number, encoded, decoded))
end

test(100) -- Original: 100.000000 Encoded: 15848.000000 Decoded: 99.941215
test(1000) --Original: 1000.000000 Encoded: 19952.000000 Decoded: 999.687729
test(10000) --Original: 10000.000000 Encoded: 25118.000000 Decoded: 9996.559632
test(100000000) --Original: 100000000.000000 Encoded: 63095.000000 Decoded: 99988360.393047
test(2846893467) --Original: 2846893467.000000 Encoded: 88193.000000 Decoded: 2846696448.133367

You have to sacrifice precision in order to save larger numbers in the same space, but that won’t be an issue if you just want to prefix the first few numbers. This is the only soultion for OrderedDatastores.

The greater the base, the larger numbers you can store. The higher the precision, the smaller numbers you can store. Find a balance that works best for your game.


As a side note, I’d recommend editing your previous replies instead of posting new ones to help reduce clutter.

3 Likes

So for somewhat reason every time a player joins the game his money is not zero and the leaderboard is monthly.

How did you modify the source code? This is likely due to something you did, as the leaderboard by itself still works completely as expected. Also, money is not something set by the global leaderboard.

I just wrote down the code everything works fine, but it wont be monthly like the leaderboard. It is showing my coins I have, but the thing is I want it to be at zero