Hey I almost fixed my previous issue, however now I am stuck with the board not auto-refreshing, but it only refreshes after all players leave and join back, so like on a new server. I wanted it to auto refresh every x time, so like every 1 minute or something.
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetOrderedDataStore("cashds_1")
local surfaceGui = script.Parent
local sample = script:WaitForChild("Sample")
local sf = surfaceGui:WaitForChild("ScrollingFrame")
local uiListLayout = sf:FindFirstChildOfClass("UIListLayout") or Instance.new("UIListLayout")
uiListLayout.SortOrder = Enum.SortOrder.LayoutOrder
uiListLayout.Parent = sf
local existingLabels = {} -- Use a dictionary to keep track of existing labels
local function savePlayerCash(player)
print("saving player cash")
local key = tostring(player.UserId)
local success, result = pcall(dataStore.SetAsync, dataStore, key, player.leaderstats.Cash.Value)
if not success then
warn("[Global Leaderboard] Error saving cash for player", player.UserId, ":", result)
end
end
local function getTopPlayers()
print("getting top players")
local success, pages = pcall(dataStore.GetSortedAsync, dataStore, false, 100)
if not success then
warn("[Global Leaderboard] Error getting top players:", pages)
return nil
end
return pages:GetCurrentPage()
end
local function updateLeaderboard()
print("[Global Leaderboard] Updating leaderboard at", os.date("%Y-%m-%d %H:%M:%S"))
local top = getTopPlayers()
if not top then
return
end
-- Clear existing labels
for _, label in pairs(existingLabels) do
label:Destroy()
end
existingLabels = {} -- Reset the dictionary
for rank, entry in ipairs(top) do
local userId = entry.key
local cash = entry.value
print("[Global Leaderboard] Player at rank", rank, "with UserId", userId, "has cash value", cash)
local new = sample:Clone()
new.Name = userId
new.LayoutOrder = rank
new.Parent = sf
local playerNameLabel = new:WaitForChild("playerName")
local rankLabel = new:WaitForChild("rank")
local valueLabel = new:WaitForChild("value")
playerNameLabel.Text = "Loading..."
rankLabel.Text = "#" .. rank
valueLabel.Text = cash
existingLabels[userId] = new -- Add the new label to the dictionary
coroutine.wrap(function()
local playerName = Players:GetNameFromUserIdAsync(userId)
if playerName then
playerNameLabel.Text = playerName
else
playerNameLabel.Text = "Unknown"
end
end)()
end
end
local function onPlayerAdded(player)
print("player added")
player.Changed:Connect(function(property)
if property == "Parent" and player.Parent == nil then
savePlayerCash(player)
end
end)
end
Players.PlayerAdded:Connect(onPlayerAdded)
local function autoUpdateLeaderboard()
print("updating leaderboard")
while true do
updateLeaderboard()
wait(5) -- Update every 5 seconds
end
end
autoUpdateLeaderboard()
Nothing. It just prints that user of id something has loaded with x amount of cash. However, it works correctly only when players leave and join back. In the game, when I sell and get more cash, it doesn’t auto update, the prints keep stating the same, if the player had 10 when joined the game and then sold and now has 12 cash, it still says the player has 10 but when the player leaves and joins a new server it says now the player has 12 cash. So it just doesn’t auto-update during the game.
local function onPlayerAdded(player)
print("player added")
spawn(function()
while true do
savePlayerCash(player)
wait(10) -- Saves player's cash every 10 seconds
end
end)
end
local function onPlayerRemoving(player)
print("player removed")
savePlayerCash(player)
end
Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)
Because you only save the player’s cash when he leaves the game.
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetOrderedDataStore("cashds_1")
local surfaceGui = script.Parent
local sample = script:WaitForChild("Sample")
local sf = surfaceGui:WaitForChild("ScrollingFrame")
local uiListLayout = sf:FindFirstChildOfClass("UIListLayout") or Instance.new("UIListLayout")
uiListLayout.SortOrder = Enum.SortOrder.LayoutOrder
uiListLayout.Parent = sf
local existingLabels = {} -- Use a dictionary to keep track of existing labels
local function updateLeaderboard()
print("[Global Leaderboard] Updating leaderboard at", os.date("%Y-%m-%d %H:%M:%S"))
local top = dataStore:GetSortedAsync(false, 100)
for _, label in ipairs(existingLabels) do
label:Destroy()
end
table.clear(existingLabels)
for rank, entry in ipairs(top) do
local userId = entry.key
local cash = entry.value
print(`[Global Leaderboard] Player at rank {rank} with UserId {userId} has cash value {cash}`)
local new = sample:Clone()
new.Name = userId
new.LayoutOrder = rank
new.Parent = sf
local playerNameLabel = new:FindFirstChild("playerName")
local rankLabel = new:FindFirstChild("rank")
local valueLabel = new:FindFirstChild("value")
playerNameLabel.Text = "Loading..."
rankLabel.Text = `#{rank}`
valueLabel.Text = cash
table.insert(existingLabels, new)
local playerName = Players:GetNameFromUserIdAsync(userId)
if playerName then
playerNameLabel.Text = playerName
else
playerNameLabel.Text = "Unknown"
end
end
end
local function save(player:Player)
--[[
We don't need to do all the checks here to prevent data loss.
If this was to control the player's main data, we would
use many more precautions. But since this store is just
meant to mimic player data, we do not need to do that.
]]
local success, result = pcall(dataStore.SetAsync, dataStore, player.UserId, player.leaderstats.Cash.Value)
if success then
print(`Successfully saved data for @{player.Name}.`)
else
warn(result)
end
end
local function autoUpdateLeaderboard()
print("updating leaderboard")
while true do
local success, result = pcall(updateLeaderboard)
if not success then
warn("Failed to update leaderboard.")
end
task.wait(300) -- Update every 5 minutes
end
end
task.spawn(autoUpdateLeaderboard)
Players.PlayerRemoving:Connect(save)
Try this code.
I also lengthened the time between accessing the store to stay within budget and also not cause errors from throttling, etc. We don’t want to spam the store with requests.
You do. There are two ways of calling a method:
Dot notation: does not pass itself as a parameter.
Colon notation: does pass itself as a parameter.
Since we would normally use colon notation and we aren’t here, we need to pass the store itself as a parameter since the receiving subprogram expects it.
This kinda fixed the issue, however at one point it changed the value from my cash value to 0 on leaderboard and after a second it updated to the correct one
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetOrderedDataStore("cashds_1")
local surfaceGui = script.Parent
local sample = script:WaitForChild("Sample")
local sf = surfaceGui:WaitForChild("ScrollingFrame")
local uiListLayout = sf:FindFirstChildOfClass("UIListLayout") or Instance.new("UIListLayout")
uiListLayout.SortOrder = Enum.SortOrder.LayoutOrder
uiListLayout.Parent = sf
local existingLabels = {} -- Use a dictionary to keep track of existing labels
local function savePlayerCash(player)
local key = tostring(player.UserId)
local success, result = pcall(dataStore.SetAsync, dataStore, key, player.leaderstats.Cash.Value)
if not success then
warn("[Global Leaderboard] Error saving cash for player", player.UserId, ":", result)
end
end
local function getTopPlayers()
local success, pages = pcall(dataStore.GetSortedAsync, dataStore, false, 100)
if not success then
warn("[Global Leaderboard] Error getting top players:", pages)
return nil
end
return pages:GetCurrentPage()
end
local function updateLeaderboard()
print("[Global Leaderboard] Updating leaderboard at", os.date("%Y-%m-%d %H:%M:%S"))
local top = getTopPlayers()
if not top then
return
end
-- Clear existing labels
for _, label in pairs(existingLabels) do
label:Destroy()
end
existingLabels = {} -- Reset the dictionary
for rank, entry in ipairs(top) do
local userId = entry.key
local cash = entry.value
print("[Global Leaderboard] Player at rank", rank, "with UserId", userId, "has cash value", cash)
local new = sample:Clone()
new.Name = userId
new.LayoutOrder = rank
new.Parent = sf
local playerNameLabel = new:WaitForChild("playerName")
local rankLabel = new:WaitForChild("rank")
local valueLabel = new:WaitForChild("value")
playerNameLabel.Text = "Loading..."
rankLabel.Text = "#" .. rank
valueLabel.Text = cash
existingLabels[userId] = new -- Add the new label to the dictionary
coroutine.wrap(function()
local playerName = Players:GetNameFromUserIdAsync(userId)
if playerName then
playerNameLabel.Text = playerName
else
playerNameLabel.Text = "Unknown"
end
end)()
end
end
local function onPlayerAdded(player)
player.Changed:Connect(function(property)
if property == "Parent" and player.Parent == nil then
savePlayerCash(player)
end
end)
end
Players.PlayerAdded:Connect(onPlayerAdded)
local function autoUpdateLeaderboard()
while true do
updateLeaderboard()
wait(60) -- Update every 60 seconds (1 minute)
end
end
game:GetService("RunService").Heartbeat:Connect(autoUpdateLeaderboard)
I changed the code to how it was and I only changed what TomoT said about the playeradded and removed and it kinda fixed the issue. Your fix made it all uh well not work so are you sure it all requires a change not just the part when player joins? Also are you sure this works globally? I wanted it to work you know like a global leaderboard
I’m taking parts from my own global leaderboard, which works perfectly (for me), and trying to apply them to your leaderboard. Like I said before, can you try and make it print what the error actually was? It’s hard to debug without it. What do you mean TomoT “kind of” fixed the issue? What doesn’t work with it? I would really like to try and help further.
It looks like it didn’t success on updating the leaderboard. The warn came from here. Also, what I meant is I used the same script I originally posted in this topic, changed only what TomoT said to change when player joins (just the part) and uh it kinda worked for the refreshing, but it sometimes bugs out and the value of a player can be set to 0 for a brief moment and then goes back to what it should be, also idk why but the first 3 places aren’t colored.
Also one more thing, the value was stuck for like good 3 seconds like this:
(im the guy with 0)
then it went to this. I’m not sure if it should work like that (im back to the old “working” version of the script, the one I told you above that i “fixed” based on tomot comment, while im still trying to find out why yours doesn’t work)
I’m aware, I asked if you could make it show the error, so something like
if not success then
warn("Failed with error: "..result)
end
For the refreshing, do you mean that the refreshed values are outdated? Do they not show at all? They won’t colour because you didn’t write that in your script. The delay might be to do with the fact you are using a coroutine. For my leaderboard, I didn’t use a coroutine, and it took about half a second between each entry showing, just for context.
Uh I found another problem right now. I have no idea what is wrong because everything was working on my old script but now out of nowhere my values keep glitching. Every refresh it keeps changing from 34 to 38 then 34 then 38 like old value to new value everytime it refreshes.
Moving onto your version and the warn, i found this error
I feel a little bit stupid… it’s so simple… XD
I forgot to get the current page.
Change the main iterator a bit:
--from:
for rank, entry in ipairs(top) do
--to this:
for rank, entry in ipairs(top:GetCurrentPage()) do
I think the issue with the other script is how short the save interval is. This not only busts through your budget incredibly fast, but may cause incorrect values to save.
Looks like the leaderboard started to show up again. However same issue with auto refreshing doesn’t work. Maybe I should just do what TomoT said? To add a while loop when players enter? Also, are you sure it works as a GLOBAL leaderboard? When 2 players are on different servers and both sell, does it update for both of them? I tried checking on a vip server but that’s when it glitched my values.
I think you need to try debouncing your leaderboard.
local db = false
local function updateLeaderboard()
if db then return nil end
db = true
--update leaderboard code goes here
db = false
end
You could also try checking if their frame already exists:
if sf:FindFirstChild(userId) then continue end
Yes. That’s the whole reason we are using a data store for this. An OrderedDataStore just means that it will order the store, and multiple servers can retrieve (GetSortedAsync) the data.
Also, please make sure you don’t spam update your leaderboard, it will cause so many issues when it comes to actual data saving and memory usage. You might need to decide whether it’s better to have a constantly updated leaderboard or good memory usage and player’s data saving working well.
Just refresh the leaderboard periodically, use a loop that calls the updateLeaderboard function at regular intervals using wait(timeInSeconds) .
Make sure the leaderboard updates are triggered only when necessary, such as when a player’s cash value changes or when a new player joins.