I need to fix the problem which is I can see the leaderboard working on Roblox Studio but not on Roblox Player Client, and I want to know what is causing the issue to solve this in future tests.
When I play the Roblox Client it just shows up the Loading message.
I tried assistant to help me but I haven’t got so far…
local SCRIPT_NAME = "WinsChartDisplay" -- For logging
-- Services
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local HttpService = game:GetService("HttpService")
local RunService = game:GetService("RunService")
-- Configuration
local DEBUG_MODE = true -- Set to false to reduce console output
local SHOW_TOP_PLAYER_AVATAR = true
local UPDATE_INTERVAL_MINUTES = 0.5 -- How often to refresh the leaderboard from DataStore (0.5 minutes = 30 seconds)
local GLOBAL_STATS_DATA_STORE_NAME = "GlobalStatsData_v1"
local MOST_WINS_VALUE_KEY = "MostWinsEverValue"
local MOST_WINS_PLAYER_ID_KEY = "MostWinsEverPlayerUserId"
local MOST_WINS_PLAYER_NAME_KEY = "MostWinsEverPlayerName"
local PLAYER_WINS_ORDERED_DATA_STORE_NAME = "PlayerWinsOrdered_v1" -- Must match WinsLeaderboardManager
local MAX_LEADERBOARD_ENTRIES = 10 -- Number of entries to show on the SurfaceGui
local DEFAULT_3D_TEXT_PARAMS = {
Text = "0",
TextSize = 10,
TextColor3 = "255,255,255", -- RGB string
Extrude = 0.5,
Font = "SourceSans",
AnchorPoint = "Center"
}
-- References to Model Components
local leaderboardModel = script.Parent
local threeDTextObject = leaderboardModel:FindFirstChild("ThreeDTextObject")
local threeDTextParams = threeDTextObject and threeDTextObject:FindFirstChild("ThreeDTextParams")
local firstPlaceAvatarModel = leaderboardModel:FindFirstChild("First Place Avatar")
local firstPlaceRig = firstPlaceAvatarModel and firstPlaceAvatarModel:FindFirstChild("Rig")
local firstPlaceHumanoid = firstPlaceRig and firstPlaceRig:FindFirstChildOfClass("Humanoid")
local animationInstance = firstPlaceAvatarModel and firstPlaceAvatarModel:FindFirstChild("AnimationToPlay")
local scoreBlock = leaderboardModel:FindFirstChild("ScoreBlock")
local noApiSurfaceGui = scoreBlock and scoreBlock:FindFirstChild("NoAPIServices")
local noDataFoundSurfaceGui = scoreBlock and scoreBlock:FindFirstChild("NoDataFound")
local leaderboardSurfaceGui = scoreBlock and scoreBlock:FindFirstChild("Leaderboard")
local namesFolder = leaderboardSurfaceGui and leaderboardSurfaceGui:FindFirstChild("Names")
local photosFolder = leaderboardSurfaceGui and leaderboardSurfaceGui:FindFirstChild("Photos")
local scoreFolder = leaderboardSurfaceGui and leaderboardSurfaceGui:FindFirstChild("Score")
local updateBoardTimerPart = leaderboardModel:FindFirstChild("UpdateBoardTimer")
local timerGui = updateBoardTimerPart and updateBoardTimerPart:FindFirstChild("Timer")
local timerLabel = timerGui and timerGui:FindFirstChild("TextLabel")
local globalStatsDataStore
local playerWinsOrderedDataStore
-- Helper for debug printing
local function debugPrint(...)
if DEBUG_MODE then
print(SCRIPT_NAME .. ":", ...)
end
end
-- Forward declaration for initializeDataStore to use update3DText
local update3DText
local updateFirstPlaceAvatar -- Forward declare
-- Initial checks for essential components
if not leaderboardModel then warn(SCRIPT_NAME .. ": CRITICAL - Script is not parented correctly.") return end
if not threeDTextObject then warn(SCRIPT_NAME .. ": ThreeDTextObject not found in", leaderboardModel.Name) end
if not threeDTextParams then warn(SCRIPT_NAME .. ": ThreeDTextParams StringValue not found in ThreeDTextObject.") end
if not firstPlaceAvatarModel then warn(SCRIPT_NAME .. ": 'First Place Avatar' model not found in", leaderboardModel.Name) end
if not firstPlaceRig then warn(SCRIPT_NAME .. ": 'Rig' model not found in 'First Place Avatar'.") end
if not firstPlaceHumanoid then warn(SCRIPT_NAME .. ": Humanoid not found in 'Rig'.") end
if not animationInstance then warn(SCRIPT_NAME .. ": 'AnimationToPlay' Animation not found in 'First Place Avatar'.") end
if not scoreBlock then warn(SCRIPT_NAME .. ": 'ScoreBlock' not found in", leaderboardModel.Name) end
if not noApiSurfaceGui then warn(SCRIPT_NAME .. ": 'NoAPIServices' GUI not found in ScoreBlock.") end
if not noDataFoundSurfaceGui then warn(SCRIPT_NAME .. ": 'NoDataFound' GUI not found in ScoreBlock.") end
if not leaderboardSurfaceGui then warn(SCRIPT_NAME .. ": 'Leaderboard' SurfaceGui not found in ScoreBlock.") end
if not namesFolder then warn(SCRIPT_NAME .. ": 'Names' Folder not found in Leaderboard SurfaceGui.") end
if not photosFolder then warn(SCRIPT_NAME .. ": 'Photos' Folder not found in Leaderboard SurfaceGui.") end
if not scoreFolder then warn(SCRIPT_NAME .. ": 'Score' Folder not found in Leaderboard SurfaceGui.") end
if not updateBoardTimerPart then warn(SCRIPT_NAME .. ": 'UpdateBoardTimer' part not found in", leaderboardModel.Name) end
if not timerLabel then warn(SCRIPT_NAME .. ": Timer TextLabel not found in UpdateBoardTimer.") end
function update3DText(textValue)
if not threeDTextParams then
warn(SCRIPT_NAME .. ": Cannot update 3D text, ThreeDTextParams StringValue is missing.")
return
end
local newParamsTable = {}
local currentParamsStr = threeDTextParams.Value
local decodedParams = nil
local decodeSuccess = false
if type(currentParamsStr) == "string" and #currentParamsStr > 0 then
if string.sub(currentParamsStr, 1, 1) == "{" then
decodeSuccess, decodedParams = pcall(function() return HttpService:JSONDecode(currentParamsStr) end)
if not decodeSuccess then
warn(SCRIPT_NAME .. ": Failed to JSONDecode existing ThreeDTextParams.Value:", decodedParams, "Using defaults.")
end
else
debugPrint("ThreeDTextParams.Value is a non-JSON string:", currentParamsStr, "Using defaults.")
end
end
if decodeSuccess and type(decodedParams) == "table" then
newParamsTable = decodedParams
else
for k, v in DEFAULT_3D_TEXT_PARAMS do
newParamsTable[k] = v
end
debugPrint("Using default 3D text parameters because existing params were invalid or empty.")
end
newParamsTable.Text = tostring(textValue or DEFAULT_3D_TEXT_PARAMS.Text)
local encodeSuccess, newParamsStr = pcall(function() return HttpService:JSONEncode(newParamsTable) end)
if encodeSuccess then
threeDTextParams.Value = newParamsStr
debugPrint("Updated 3DTextParams (JSON) to:", newParamsStr)
else
warn(SCRIPT_NAME .. ": Failed to JSONEncode new params:", newParamsStr, ". Fallback to raw text.")
threeDTextParams.Value = newParamsTable.Text
end
end
function updateFirstPlaceAvatar(playerId, playerName)
if not firstPlaceAvatarModel then
warn(SCRIPT_NAME .. ": First place avatar model is missing, cannot update.")
return
end
if not firstPlaceRig or not firstPlaceHumanoid then
warn(SCRIPT_NAME .. ": First place avatar rig or humanoid are missing.")
if firstPlaceAvatarModel.Parent ~= nil then firstPlaceAvatarModel.Parent = nil end
return
end
if SHOW_TOP_PLAYER_AVATAR and playerId then
local numericPlayerId = tonumber(playerId)
if not numericPlayerId then
warn(SCRIPT_NAME .. ": PlayerId '" .. tostring(playerId) .. "' is not a valid number. Cannot load avatar.")
if firstPlaceAvatarModel.Parent ~= nil then firstPlaceAvatarModel.Parent = nil end
return
end
debugPrint("Attempting to load avatar for UserId:", numericPlayerId, "Name:", playerName or "N/A")
if firstPlaceAvatarModel.Parent == nil then firstPlaceAvatarModel.Parent = leaderboardModel end
local avatarLoadSuccess, loadResult = pcall(function()
local desc = Players:GetHumanoidDescriptionFromUserId(numericPlayerId)
if desc then
debugPrint("Successfully got HumanoidDescription for UserId:", numericPlayerId)
firstPlaceHumanoid:ApplyDescription(desc)
debugPrint("Applied HumanoidDescription for UserId:", numericPlayerId)
local nameTagGui = firstPlaceAvatarModel:FindFirstChild("FirstPlaceTag")
if nameTagGui and nameTagGui:IsA("BillboardGui") then
local nameLabel = nameTagGui:FindFirstChildOfClass("TextLabel") or nameTagGui:FindFirstChild("PlayerName")
if not nameLabel and nameTagGui:FindFirstChild("ImageLabel") then nameLabel = nameTagGui.ImageLabel:FindFirstChildOfClass("TextLabel") end
if nameLabel then
nameLabel.Text = playerName or ("ID: " .. tostring(numericPlayerId))
debugPrint("Updated name tag for UserId:", numericPlayerId, "to:", nameLabel.Text)
else
debugPrint("Name tag TextLabel not found in FirstPlaceTag for UserId:", numericPlayerId)
end
else
debugPrint("FirstPlaceTag BillboardGui not found in firstPlaceAvatarModel for UserId:", numericPlayerId)
end
if animationInstance and animationInstance:IsA("Animation") then
local animator = firstPlaceHumanoid:FindFirstChildOfClass("Animator")
if animator then
debugPrint("Attempting to play animation directly for UserId:", numericPlayerId)
for _, track in animator:GetPlayingAnimationTracks() do
track:Stop(0.1)
end
local animTrackLoadSuccess, animationTrack = pcall(function()
return animator:LoadAnimation(animationInstance)
end)
if animTrackLoadSuccess and animationTrack then
local animPlaySuccess, playErr = pcall(function() animationTrack:Play() end)
if not animPlaySuccess then
warn(SCRIPT_NAME .. ": Error playing loaded animation directly for UserId:", numericPlayerId, "-", playErr)
else
debugPrint("Successfully started animation directly for UserId:", numericPlayerId)
end
else
warn(SCRIPT_NAME .. ": Error loading animation directly onto animator for UserId:", numericPlayerId, "-", animationTrack)
end
else
warn(SCRIPT_NAME .. ": Animator not found on Humanoid, cannot play animation for UserId:", numericPlayerId)
end
else
warn(SCRIPT_NAME .. ": Animation instance not found or not an Animation object for avatar of UserId:", numericPlayerId)
end
else
-- If desc is nil, throw an error to make the pcall fail.
error("HumanoidDescription was nil for UserId: " .. tostring(numericPlayerId))
end
end)
if not avatarLoadSuccess then
warn(SCRIPT_NAME .. ": Error during avatar loading/setup process for UserId:", numericPlayerId, "-", loadResult)
if firstPlaceAvatarModel.Parent ~= nil then firstPlaceAvatarModel.Parent = nil end
end
else
debugPrint("Hiding first place avatar. SHOW_TOP_PLAYER_AVATAR:", SHOW_TOP_PLAYER_AVATAR, "PlayerId:", tostring(playerId))
if firstPlaceAvatarModel.Parent ~= nil then
firstPlaceAvatarModel.Parent = nil
local animator = firstPlaceHumanoid:FindFirstChildOfClass("Animator")
if animator then
for _, track in animator:GetPlayingAnimationTracks() do track:Stop(0.1) end
end
end
end
end
local function initializeDataStore()
debugPrint("Attempting to initialize DataStores...")
if not RunService:IsStudio() and not DataStoreService:IsApiAccessEnabled() then
warn(SCRIPT_NAME .. ": DataStore API access is not enabled. Leaderboard will not function.")
if noApiSurfaceGui then noApiSurfaceGui.Enabled = true end
if leaderboardSurfaceGui then leaderboardSurfaceGui.Enabled = false end
if noDataFoundSurfaceGui then noDataFoundSurfaceGui.Enabled = false end
if timerLabel then timerLabel.Text = "API Error" end
if updateFirstPlaceAvatar then updateFirstPlaceAvatar(nil, nil) end
if update3DText then update3DText("API N/A") end
return false
end
if noApiSurfaceGui then noApiSurfaceGui.Enabled = false end
local dsInitialized = true
local successGlobal, dsGlobal = pcall(function() return DataStoreService:GetDataStore(GLOBAL_STATS_DATA_STORE_NAME) end)
if successGlobal then
globalStatsDataStore = dsGlobal
debugPrint("Successfully connected to GlobalStatsDataStore:", GLOBAL_STATS_DATA_STORE_NAME)
else
warn(SCRIPT_NAME .. ": Failed to connect to GlobalStatsDataStore:", GLOBAL_STATS_DATA_STORE_NAME, "-", dsGlobal)
dsInitialized = false
end
local successOrdered, dsOrdered = pcall(function() return DataStoreService:GetOrderedDataStore(PLAYER_WINS_ORDERED_DATA_STORE_NAME) end)
if successOrdered then
playerWinsOrderedDataStore = dsOrdered
debugPrint("Successfully connected to PlayerWinsOrderedDataStore:", PLAYER_WINS_ORDERED_DATA_STORE_NAME)
else
warn(SCRIPT_NAME .. ": Failed to connect to PlayerWinsOrderedDataStore:", PLAYER_WINS_ORDERED_DATA_STORE_NAME, "-", dsOrdered)
dsInitialized = false
end
if not dsInitialized then
if timerLabel then timerLabel.Text = "DS Conn. Error" end
if noDataFoundSurfaceGui then noDataFoundSurfaceGui.Enabled = true end
if leaderboardSurfaceGui then leaderboardSurfaceGui.Enabled = false end
if updateFirstPlaceAvatar then updateFirstPlaceAvatar(nil, nil) end
if update3DText then update3DText("DS Error") end
end
return dsInitialized
end
local function updateLeaderboardSurfaceGui(topPlayersData)
if not leaderboardSurfaceGui or not namesFolder or not photosFolder or not scoreFolder then
warn(SCRIPT_NAME .. ": SurfaceGui leaderboard components missing, cannot update.")
if leaderboardSurfaceGui then leaderboardSurfaceGui.Enabled = false end
return
end
if not topPlayersData or #topPlayersData == 0 then
debugPrint("No player data for SurfaceGui, disabling.")
leaderboardSurfaceGui.Enabled = false
return
end
leaderboardSurfaceGui.Enabled = true
debugPrint("Updating SurfaceGui leaderboard with", #topPlayersData, "entries.")
for i = 1, MAX_LEADERBOARD_ENTRIES do
local nameLabel = namesFolder:FindFirstChild("Name" .. i)
local photoLabel = photosFolder:FindFirstChild("Photo" .. i)
local scoreLabel = scoreFolder:FindFirstChild("Score" .. i)
if not (nameLabel and photoLabel and scoreLabel) then
debugPrint("Missing GUI elements for slot", i, "- skipping.")
continue
end
local playerData = topPlayersData[i]
if playerData then
nameLabel.Text = playerData.Name or "Error"
scoreLabel.Text = tostring(playerData.Wins or "-")
photoLabel.Image = ""
nameLabel.Visible = true
photoLabel.Visible = true
scoreLabel.Visible = true
local success, thumbUrl = pcall(Players.GetUserThumbnailAsync, Players, playerData.UserId, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size48x48)
if success and thumbUrl then
photoLabel.Image = thumbUrl
debugPrint("Slot", i, ": Set photo for", playerData.Name, "(", playerData.UserId, ")")
else
photoLabel.Image = ""
warn(SCRIPT_NAME .. ": Failed to get thumbnail for UserId:", playerData.UserId, "-", thumbUrl)
end
else
nameLabel.Text = ""
scoreLabel.Text = ""
photoLabel.Image = ""
nameLabel.Visible = false
photoLabel.Visible = false
scoreLabel.Visible = false
end
end
end
local function updateDisplay()
debugPrint("updateDisplay called. Resetting UI to loading state.")
-- Initial UI reset
if update3DText then update3DText("Loading...") end
if updateFirstPlaceAvatar then updateFirstPlaceAvatar(nil, nil) end
if leaderboardSurfaceGui then leaderboardSurfaceGui.Enabled = false end
if noDataFoundSurfaceGui then noDataFoundSurfaceGui.Enabled = false end
if noApiSurfaceGui and noApiSurfaceGui.Enabled and (RunService:IsStudio() or DataStoreService:IsApiAccessEnabled()) then
-- Turn off API error GUI if API is actually available now (initializeDataStore will manage it if API is off)
noApiSurfaceGui.Enabled = false
end
if not globalStatsDataStore or not playerWinsOrderedDataStore then
debugPrint("A DataStore is not initialized. Skipping update.")
if noDataFoundSurfaceGui then noDataFoundSurfaceGui.Enabled = true end
if leaderboardSurfaceGui then leaderboardSurfaceGui.Enabled = false end
if update3DText then update3DText("No Data") end
if updateFirstPlaceAvatar then updateFirstPlaceAvatar(nil, nil) end
return
end
-- Update 3D Text and First Place Avatar (Global Top 1)
local mostWinsValue, fetchedPlayerId, fetchedPlayerName
local valSuccess, valResult = pcall(function() return globalStatsDataStore:GetAsync(MOST_WINS_VALUE_KEY) end)
local idSuccess, idResult = pcall(function() return globalStatsDataStore:GetAsync(MOST_WINS_PLAYER_ID_KEY) end)
local nameSuccess, nameResult
if valSuccess and idSuccess and valResult ~= nil and idResult ~= nil then
mostWinsValue = valResult
fetchedPlayerId = idResult
nameSuccess, nameResult = pcall(function() return globalStatsDataStore:GetAsync(MOST_WINS_PLAYER_NAME_KEY) end)
if nameSuccess and nameResult ~= nil then
fetchedPlayerName = nameResult
else
-- Attempt to get live name if not found in datastore or if fetch failed
local numericFetchedPlayerId = tonumber(fetchedPlayerId)
if numericFetchedPlayerId then
local liveNameSuccess, livePlayerName = pcall(Players.GetNameFromUserIdAsync, Players, numericFetchedPlayerId)
if liveNameSuccess and livePlayerName then
fetchedPlayerName = livePlayerName
else
fetchedPlayerName = "N/A"
warn(SCRIPT_NAME .. ": Error fetching '", MOST_WINS_PLAYER_NAME_KEY, "':", nameResult, " and live name fetch failed for ID", numericFetchedPlayerId, ":", livePlayerName)
end
else
fetchedPlayerName = "Invalid ID"
warn(SCRIPT_NAME .. ": Player ID from datastore '", tostring(fetchedPlayerId), "' is not a valid number for name lookup.")
end
end
debugPrint("Displaying Global Most Wins Value:", mostWinsValue, "PlayerId:", fetchedPlayerId, "PlayerName:", fetchedPlayerName)
if update3DText then update3DText(mostWinsValue) end
if updateFirstPlaceAvatar then updateFirstPlaceAvatar(fetchedPlayerId, fetchedPlayerName) end
if noDataFoundSurfaceGui then noDataFoundSurfaceGui.Enabled = false end -- Data found for top player
else
debugPrint("Failed to fetch complete global data or data is nil. valSuccess:", tostring(valSuccess), "idSuccess:", tostring(idSuccess), "valResult:", tostring(valResult), "idResult:", tostring(idResult))
if update3DText then update3DText("N/A") end
if updateFirstPlaceAvatar then updateFirstPlaceAvatar(nil, nil) end
if noDataFoundSurfaceGui then noDataFoundSurfaceGui.Enabled = true end -- Indicate data issue for top player
end
-- Update SurfaceGui Leaderboard (Top N)
local topPlayersList = {}
local pagesSuccess, pages = pcall(function()
return playerWinsOrderedDataStore:GetSortedAsync(false, MAX_LEADERBOARD_ENTRIES)
end)
if pagesSuccess and pages then
local entriesProcessed = 0
repeat
local currentPageSuccess, currentPage = pcall(function() return pages:GetCurrentPage() end)
if currentPageSuccess and currentPage then
for _, entry in currentPage do
entriesProcessed = entriesProcessed + 1
if entriesProcessed > MAX_LEADERBOARD_ENTRIES then break end
local userId = tonumber(entry.key) -- Ensure userId is a number for GetNameFromUserIdAsync
local wins = entry.value
local playerName = "Player"
if userId then -- Only proceed if userId is a valid number
local pNameSuccess, pNameResult = pcall(Players.GetNameFromUserIdAsync, Players, userId)
if pNameSuccess and pNameResult then
playerName = pNameResult
else
warn(SCRIPT_NAME .. ": Could not get name for UserId:", userId, "Error:", pNameResult, ". Using default.")
playerName = "ID: " .. tostring(userId)
end
table.insert(topPlayersList, { UserId = userId, Name = playerName, Wins = wins })
else
warn(SCRIPT_NAME .. ": Invalid UserId key in OrderedDataStore entry:", entry.key)
end
end
if entriesProcessed >= MAX_LEADERBOARD_ENTRIES or pages.IsFinished then
break
else
local advanceSuccess, advanceErr = pcall(pages.AdvanceToNextPageAsync, pages)
if not advanceSuccess then
warn(SCRIPT_NAME .. ": Error advancing page in OrderedDataStore:", advanceErr)
break
end
end
else
warn(SCRIPT_NAME .. ": Error getting current page from OrderedDataStore:", currentPage)
break
end
until false
debugPrint("Fetched", #topPlayersList, "players for SurfaceGui leaderboard.")
updateLeaderboardSurfaceGui(topPlayersList) -- This will enable/disable leaderboardSurfaceGui
if noDataFoundSurfaceGui then
if #topPlayersList == 0 and (not valSuccess or not idSuccess or valResult == nil or idResult == nil) then
noDataFoundSurfaceGui.Enabled = true
elseif #topPlayersList > 0 then
noDataFoundSurfaceGui.Enabled = false
end
end
else
warn(SCRIPT_NAME .. ": Failed to fetch top players from OrderedDataStore. Error:", pages)
if noDataFoundSurfaceGui then noDataFoundSurfaceGui.Enabled = true end
if leaderboardSurfaceGui then leaderboardSurfaceGui.Enabled = false end
updateLeaderboardSurfaceGui({})
end
end
-- Initialization and Main Loop
debugPrint("Script started. Initializing...")
if initializeDataStore() then
debugPrint("DataStores initialized successfully. Starting main loop.")
updateDisplay()
local updateIntervalSeconds = UPDATE_INTERVAL_MINUTES * 60
if updateIntervalSeconds < 10 then updateIntervalSeconds = 10 end
debugPrint("Leaderboard update interval set to:", updateIntervalSeconds, "seconds.")
local timeUntilNextUpdate = updateIntervalSeconds
while true do
if timerLabel then
local minutes = math.floor(timeUntilNextUpdate / 60)
local seconds = math.floor(timeUntilNextUpdate % 60)
timerLabel.Text = string.format("Next update: %d:%02d", minutes, seconds)
end
if timeUntilNextUpdate <= 0 then
debugPrint("Update interval reached. Updating display.")
if not globalStatsDataStore or not playerWinsOrderedDataStore then
debugPrint("A DataStore became nil, re-initializing...")
if not initializeDataStore() then
timeUntilNextUpdate = updateIntervalSeconds
if timerLabel then timerLabel.Text = "DS Error" end
updateDisplay()
task.wait(1)
timeUntilNextUpdate = timeUntilNextUpdate - 1
continue
end
end
updateDisplay()
timeUntilNextUpdate = updateIntervalSeconds
end
task.wait(1)
timeUntilNextUpdate = timeUntilNextUpdate - 1
end
else
warn(SCRIPT_NAME .. ": Could not initialize DataStores during startup. Display will show errors.")
updateDisplay()
if timerLabel and timerLabel.Text == "" then
timerLabel.Text = "Board Init Error"
end
end
debugPrint("Script finished its main setup path (or failed initialization).")