local Players=game:GetService('Players')
local TempPart=script.Parent
--[[]]--
local SurfaceGui=TempPart.SurfaceGui
local MainFrame=SurfaceGui.Frame
--[[]]--
local Template=script['Template']
--[[]]--
local RecentlySaved={}
local DataStoreService=game:GetService('DataStoreService')
local GlobalLeaderboardB=DataStoreService:GetOrderedDataStore(":DataStore")
if not game["Run Service"]:IsStudio() then
GlobalLeaderboardB=DataStoreService:GetOrderedDataStore("Game:DataStore")
end
--[[ SETTINGS ]]--
local CurrencyName="Best Time"
local ListSize=100
local UpdateEvery=120
local MinimumRequirement=100
--[[]]--
local _functions={}
_functions.returncurrency = function(v)
local x=0
for a, b in pairs(v:GetDescendants()) do
if b.Name==CurrencyName then
x=b.Value
break
end
end
return x
end
_functions.removefromrecentlysaved=function(PlayerName)
for i, v in pairs(RecentlySaved) do
if v==PlayerName then
table.remove(RecentlySaved,i)
break
end
end
end
_functions.autoremoverecentsaved=function(PlayerName)
spawn(function()
wait(15)
for i, v in pairs(RecentlySaved) do
if v==PlayerName then
table.remove(RecentlySaved,i)
break
end
end
end)
end
_functions.returnplayerlist=function()
local int = 0
for i, v in pairs(Players:GetPlayers()) do
for _, j in pairs(v:GetDescendants()) do
if j.Name==CurrencyName and j.Value>MinimumRequirement then
int+=1
break
end
end
end
return int
end
_functions.clear=function()
for i, v in pairs(MainFrame:GetChildren()) do
if v:IsA('Frame') then
v:Destroy()
end
end
end
_functions.abbreviate=function(value,idp)
if value < 1000 then
return math.floor(value + 0.5)
else
local abbreviations = {"", "K", "M", "B", "T"}
local ex = math.floor(math.log(math.max(1, math.abs(value)),1000))
--[[]]--
local abbrevs = abbreviations [1 + ex] or ("e+"..ex)
local normal = math.floor(value * ((10 ^ idp) / (1000 ^ ex))) / (10 ^ idp)
--[[]]--
return ("%."..idp.."f%s"):format(normal, abbrevs)
end
end
--[[]]--
Players.PlayerRemoving:Connect(function(Player)
local PName=Player.Name
if not table.find(RecentlySaved,Player.Name) then
table.insert(RecentlySaved,Player.Name)
local CurrentCurrencyAmount=_functions.returncurrency(Player)
local Success,Errormsg=pcall(function()
GlobalLeaderboardB:SetAsync(Player.UserId,CurrentCurrencyAmount)
end)
if not Success then warn(Errormsg) end
_functions.removefromrecentlysaved(PName)
else
end
end)
--[[]]--
while true do
task.wait()
_functions.clear()
repeat wait() until _functions.returnplayerlist()>0
for i, Player in pairs(Players:GetPlayers()) do
local PName=Player.Name
if not table.find(RecentlySaved,Player.Name) then
table.insert(RecentlySaved,Player.Name)
local CurrentCurrencyAmount=_functions.returncurrency(Player)
local Success,Errormsg=pcall(function()
GlobalLeaderboardB:SetAsync(Player.UserId,CurrentCurrencyAmount)
end)
if not Success then warn(Errormsg) end
_functions.autoremoverecentsaved(PName)
end
end
local Pages = GlobalLeaderboardB:GetSortedAsync(false, ListSize)
local TopList = Pages:GetCurrentPage()
for Rank, SavedData in ipairs(TopList) do
local UserId = SavedData.key
local CAmount=SavedData.value
--[[]]--
if CAmount>=MinimumRequirement then
local Template=script['Template']:Clone()
Template.Parent=MainFrame
Template.LayoutOrder=Rank
Template.Score.Text="".._functions.abbreviate(CAmount,1)
local function SetRankText(n)
local A={}
A[1]=function()
Template.Rank.Text="🥇"
end
A[2]=function()
Template.Rank.Text="🥈"
end
A[3]=function()
Template.Rank.Text="🥉"
end
if n<=#A then
A[n]()
else Template.Rank.Text="#"..n
end
end
SetRankText(Rank)
--[[]]--
local User="Unknown"
local Success,Errormsg=pcall(function()
User=Players:GetNameFromUserIdAsync(UserId)
end)
Template.Username.Text=User
Template.Name=User
end
end
task.wait(UpdateEvery)
end
local Players = game:GetService('Players')
local TempPart = script.Parent
local SurfaceGui = TempPart.SurfaceGui
local MainFrame = SurfaceGui.Frame
local Template = script:WaitForChild('Template')
local RecentlySaved = {}
local DataStoreService = game:GetService('DataStoreService')
local CurrencyName = "Best Time"
local ListSize = 100
local UpdateEvery = 120
local MinimumRequirement = 100
local GlobalLeaderboardB = DataStoreService:GetOrderedDataStore(game["Run Service"]:IsStudio() and ":DataStore" or "Game:DataStore")
local function returnCurrency(player)
for _, descendant in pairs(player:GetDescendants()) do
if descendant.Name == CurrencyName then
return descendant.Value
end
end
return 0
end
local function removePlayerFromRecentlySaved(playerName)
for i, name in ipairs(RecentlySaved) do
if name == playerName then
table.remove(RecentlySaved, i)
break
end
end
end
local function autoRemoveFromRecentlySaved(playerName)
task.spawn(function()
wait(15)
removePlayerFromRecentlySaved(playerName)
end)
end
local function returnPlayerList()
local count = 0
for _, player in ipairs(Players:GetPlayers()) do
if returnCurrency(player) > MinimumRequirement then
count = count + 1
end
end
return count
end
local function clearMainFrame()
for _, child in ipairs(MainFrame:GetChildren()) do
if child:IsA('Frame') then
child:Destroy()
end
end
end
local function abbreviateValue(value, idp)
if value < 1000 then
return math.floor(value + 0.5)
else
local abbreviations = {"", "K", "M", "B", "T"}
local ex = math.floor(math.log(math.max(1, math.abs(value)), 1000))
local abbrevs = abbreviations[1 + ex] or ("e+"..ex)
local normal = math.floor(value * ((10 ^ idp) / (1000 ^ ex))) / (10 ^ idp)
return ("%."..idp.."f%s"):format(normal, abbrevs)
end
end
local function setRankText(template, rank)
local rankSymbols = {"🥇", "🥈", "🥉"}
template.Rank.Text = rank <= 3 and rankSymbols[rank] or "#" .. rank
end
Players.PlayerRemoving:Connect(function(player)
local playerName = player.Name
if not table.find(RecentlySaved, playerName) then
table.insert(RecentlySaved, playerName)
local currentCurrencyAmount = returnCurrency(player)
local success, errorMsg = pcall(function()
GlobalLeaderboardB:SetAsync(player.UserId, currentCurrencyAmount)
end)
if not success then
warn(errorMsg)
end
removePlayerFromRecentlySaved(playerName)
end
end)
while true do
task.wait()
clearMainFrame()
repeat
wait()
until returnPlayerList() > 0
for _, player in ipairs(Players:GetPlayers()) do
local playerName = player.Name
if not table.find(RecentlySaved, playerName) then
table.insert(RecentlySaved, playerName)
local currentCurrencyAmount = returnCurrency(player)
local success, errorMsg = pcall(function()
GlobalLeaderboardB:SetAsync(player.UserId, currentCurrencyAmount)
end)
if not success then
warn(errorMsg)
end
autoRemoveFromRecentlySaved(playerName)
end
end
local pages = GlobalLeaderboardB:GetSortedAsync(false, ListSize)
local topList = pages:GetCurrentPage()
for rank, data in ipairs(topList) do
local userId, cAmount = data.key, data.value
if cAmount >= MinimumRequirement then
local template = Template:Clone()
template.Parent = MainFrame
template.LayoutOrder = rank
template.Score.Text = abbreviateValue(cAmount, 1)
setRankText(template, rank)
local success, username = pcall(function()
return Players:GetNameFromUserIdAsync(userId)
end)
template.Username.Text = success and username or "Unknown"
template.Name = template.Username.Text
end
end
task.wait(UpdateEvery)
end
There’s a lot going on there, and honestly it’s quite hard to read. Reviewing the code, here’s a list of things you can do:
Use an approach where one script handles all of the leaderboards. From what I can see, this script only handles one, and you’re likely to have more. You will need to set up a system where different leaderboards can format their text differently (i.e., A rank leaderboard will start with “#” and a cash leaderboard will start with “$”). This will be shown in the example video below.
Only save to the OrderedDataStore when their data changes. Set up listeners that save that specific data to the OrderedDataStore when it changes. Do this where you create the leaderstats. This will remove the need for A LOT:
:: PlayerRemoving event
:: The need to save people’s data to the OrderedDataStore when leaderboards reload.
:: The RecentlySaved table and functions.
Set up a system to ignore test dummies. When testing with multiple clients in studio, their userIds will be negative which causes leaderboard to break. Make sure to ignore saving their data to the OrderedDataStore by checking if their UserId is less than 0.
Remove the nested SetRankText() function from the loop. There are multiple ways to go about doing this, but I would put a table outside the loop and index their rank. This negates the need for a nested table and unecessary if statements.
local ranks = {
[1] = "🥇",
[2] = "🥈",
[3] = "🥉",
}
Template.Rank.Text = ranks[Rank] or `#{Rank}`
My own leaderboard system using OOP, Knit, and Promises. Hopefully this can add to how you should ideally structure your leaderboard system.