Need Help: Global Leaderboard Shows Player From The Bottom To Be The Top Of List (Solved)

(Solved) Edit 2: I heavily edited this post again and deleted unnecessary part so that maybe other people who are having the same problem could read it and actually find the solution. Thanks.

I have a problem to fix a leaderboard that would somehow make players with the worst time of speedrun(the highest in time) to be placed onto the top of the list(still shows the same speedrun time tho).

Here are the relevant scripts

Server

local function fetchGlobalLeaderboardData()
	local leaderboardData = {}

	local function getDataStorePage()
		return orderedDataStore:GetSortedAsync(true, 50):GetCurrentPage()  -- Sorting in ascending order (smallest time on top)
	end

	local success, data = retryWithExponentialBackoff(getDataStorePage, 5, false)

	if success then
		for _, entry in ipairs(data) do
			local userId = tonumber(entry.key)
			local username = "Unknown"
			if userId then
				local success, result = pcall(Players.GetNameFromUserIdAsync, Players, userId)
				if success then
					username = result
				else
					warn("Failed to get username for UserId:", userId, ":", result)
				end
			end

			local timeInSeconds = entry.value / 1000
			if timeInSeconds > MINIMUM_TIME_THRESHOLD then
				table.insert(leaderboardData, {
					userId = userId,
					username = username,
					time = timeInSeconds
				})
			end
		end
	else
		warn("Failed to fetch global leaderboard data for Tower1:", data)
	end

	-- Sort data by their time (ascending)
	table.sort(leaderboardData, function(a, b)
		return a.time < b.time
	end)

	return leaderboardData
end

-- skip the other unrelevant parts

local function updateLeaderboard()
	local success, leaderboardData = pcall(fetchGlobalLeaderboardData)
	print("Fetched leaderboard data:", leaderboardData)

	if not success then
		warn("Failed to update leaderboard:", leaderboardData)
		return
	end

	UpdateLeaderboardEventTower1:FireAllClients(leaderboardData)

	for i, data in ipairs(leaderboardData) do
		if data.time > MINIMUM_TIME_THRESHOLD then
			local userId = data.userId
			local username = data.username
			local stand

			if i == 1 then
				stand = FirstPlaceStand
			elseif i == 2 then
				stand = SecondPlaceStand
			elseif i == 3 then
				stand = ThirdPlaceStand
			end

			if stand then
				applyAppearanceToDummy(stand, userId, username, i)

				local animationId = LeaderboardTower1:WaitForChild("AnimationToPlay").AnimationId
				AnimationPlayer.playAnimation(stand, animationId)

				-- Check and grant badge if applicable
				if userId then
					local player = Players:GetPlayerByUserId(userId)
					if player then
						grantSpeedrunBadge(player, data.time)
					end
				else
					warn("Invalid UserId:", userId)
				end
			end
		end
	end
end


while true do
	updateLeaderboard()
	wait(LEADERBOARD_UPDATE_INTERVAL)
end

Client

local function updateLeaderboardGui(leaderboardData)
	print("Received leaderboard data:", leaderboardData)

	-- Sort leaderboard data by time (ascending)
	table.sort(leaderboardData, function(a, b)
		return a.time < b.time
	end)

	-- Delete the  existing entries and then create the new entries afterwards, the problem could be lying here somewhere I believe
	for _, data in ipairs(leaderboardData) do
		if data.time > MINIMUM_TIME_THRESHOLD then
			-- Delete existing entry if it exists
			local existingEntry = ListContent.Items:FindFirstChild(data.username)
			if existingEntry then
				existingEntry:Destroy()
			end

			-- Create a new entry 
			local entry = Sample:Clone()
			entry.Name = data.username
			entry.Values.Username.Text = data.username
			entry.Values.Tower1.Text = string.format("%.2f", data.time) -- Format time to 2 decimal
			entry.Parent = ListContent.Items
			entry.Visible = true
		end
	end

	-- Adjust the position of each entry
	local entries = ListContent.Items:GetChildren()
	local entryCount = 0  -- This is to track the valid entries to skip the original Sample

	for i, entry in ipairs(entries) do
		if entry ~= Sample then  -- We skip the original Sample entry
			if entry:IsA("GuiObject") then
				entryCount = entryCount + 1
				entry.LayoutOrder = entryCount

				if entry:FindFirstChild("Values") and entry.Values:FindFirstChild("Index") then
					entry.Values.Index.Text = tostring(entryCount) -- Set the index number

					-- Apply special colors based on index basically
					if entryCount == 1 then
						entry.Values.Index.TextColor3 = Color3.fromRGB(255, 215, 0) -- Gold color
					elseif entryCount == 2 then
						entry.Values.Index.TextColor3 = Color3.fromRGB(192, 192, 192) -- Silver color
					elseif entryCount == 3 then
						entry.Values.Index.TextColor3 = Color3.fromRGB(205, 127, 50) -- Bronze color
					else
						entry.Values.Index.TextColor3 = Color3.fromRGB(0, 0, 0) -- Default color
					end
				end
			end
		end
	end
end


UpdateLeaderboardEvent.OnClientEvent:Connect(function(leaderboardData)
	updateLeaderboardGui(leaderboardData)
end)

Also for anyone wondering what datastore I’m using, it’s Suphi’s Datastore thanks @5uphi !!

TLDR; 1st problem: The leaderboard in my game somehow would show players with the worst ranking to be at the top of the ranking for some reason yet still stores the same data for each username. And it keeps putting the player from the worst rankings to the top(as if it’s trying to recreate the data over again but one by one.). Weird thing is that the worst of the worst(sorry hehe) data doesn’t get updated to the top though.

Edit3: I solved the problem. Look at the marked as solved. Also I believe the exponential backoff is not important at all.

4 Likes
local function getDataStorePage()
	return orderedDataStore:GetSortedAsync(false, 50):GetCurrentPage()
end
2 Likes

So sorry, I had actually found out the solution for the leaderboard myself(and forgot to mark a solution so here it is). Turns out I just need to change the way I write the code for cloning and deleting the entries. Here are the changes I made and a comparison of the logic before and after:

Previous logic

for _, data in ipairs(leaderboardData) do
		if data.time > MINIMUM_TIME_THRESHOLD then
			-- Delete existing entry if it exists
			local existingEntry = ListContent.Items:FindFirstChild(data.username)
			if existingEntry then
				existingEntry:Destroy()
			end

			-- Create a new entry 
			local entry = Sample:Clone()
			entry.Name = data.username
			entry.Values.Username.Text = data.username
			entry.Values.Tower1.Text = string.format("%.2f", data.time) -- Format time to 2 decimal
			entry.Parent = ListContent.Items
			entry.Visible = true
		end
	end

Updated logic

for _, child in ipairs(ListContent.Items:GetChildren()) do
	if child ~= Sample and child.Name ~= Sample and child:IsA("GuiObject") then

		child:Destroy()
	end
end

-- Create new entries
for _, data in ipairs(leaderboardData) do
	if data.time > MINIMUM_TIME_THRESHOLD then


		-- Create a new entry
		local entry = Sample:Clone()
		entry.Name = data.username
		entry.Values.Username.Text = data.username
		entry.Values.Tower1.Text = string.format("%.2f", data.time) -- Format time to 2 decimal places
		entry.Parent = ListContent.Items
		entry.Visible = true

	end
end

I basically added more checks for the original Sample to make sure that the original Sample doesn’t get counted as an entry by adding a check of child.name like this(yes this is pretty unnecessary I knoww but kind of necessarry for my case)

if child ~= Sample and child.Name ~= Sample and child:IsA("GuiObject") then

And another changes made is that I separated the logic of clear all existing entries and cloning all entries(in the orderedDataStore) into 2 different ipairs loops. Because the previous method it checked the same entry twice which is not the brightest idea to say the least.

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.