I am making a global leaderboard GUI to display players with their username and amount of points. My issue is that when I play on two accounts, the score that I’m obtaining from getting the player’s data is being duplicated to every player inside the leaderboard, so every player has the same amount of points (but they don’t, according to the actual datastore). Some solutions I’ve tried to do are firing remotes to the server, but the problem is that the server cannot see the player cards on the leaderboard because I am creating them on the client. So I am trying to figure out a way to do it with my client local script. I’ve tried looking for solutions on the developer forum but nothing matches my problem specifically, so I thought I should make a post.
These are pictures of the leaderboard at the top right on two of my accounts, having the same score:
Main (TheJuliusCaesar)
local function RefreshLeaderboard()
for a,b in pairs(player:WaitForChild('PlayerGui'):WaitForChild('MainGame').Menu.LB:GetChildren()) do
if b:IsA("Frame") then
if player:WaitForChild('PlayerGui'):WaitForChild('MainGame').Menu.LB:FindFirstChild(player.Name) then
--print("Found " .. b.Name)
local pData = ServerFunction:InvokeServer("getData", b.Name)
player.PlayerGui.MainGame.Menu.LB:FindFirstChild(b.Name).ScoreAmt.Text = pData.Points
elseif player:WaitForChild('PlayerGui'):WaitForChild('MainGame').Menu.LB:FindFirstChild(b.Name) ~= player.Name then
print("Found " .. b.Name)
local pData = ServerFunction:InvokeServer("getData", b.Name)
player.PlayerGui.MainGame.Menu.LB:FindFirstChild(b.Name).ScoreAmt.Text = pData.Points
end
end
end
end
while wait() do
RefreshLeaderboard()
wait(1)
end
There’s a big issue with the RefreshLeaderboard function and its logic. First, you are looping through the leaderboard, but then for some reason it uses FindFirstChild(b.Name) when that can just be simplified down to b since you already have it? So that needs to get replaced, resulting in this:
local function RefreshLeaderboard()
for a,b in pairs(player:WaitForChild('PlayerGui'):WaitForChild('MainGame').Menu.LB:GetChildren()) do
if b:IsA("Frame") then
if player:WaitForChild('PlayerGui'):WaitForChild('MainGame').Menu.LB:FindFirstChild(player.Name) then
--print("Found " .. b.Name)
local pData = ServerFunction:InvokeServer("getData", b.Name)
b.ScoreAmt.Text = pData.Points
elseif b ~= player.Name then
print("Found " .. b.Name)
local pData = ServerFunction:InvokeServer("getData", b.Name)
b.ScoreAmt.Text = pData.Points
end
end
end
end
From there it becomes more obvious. The condition that’s supposed to check if this frame is OUR player’s frame isn’t right. It simply checks if we exist in the leaderboard, which will always be true. It should instead check if the name is the same as our player’s name. Plus, the elseif should just be a simple else. From there it also becomes obvious that the if condition always does the same thing! Not sure how or why this is the case, but I’ll currently just leave it like that. This gives me the clue that the issue could lie within the RemoteFunction that’s returning the player data. It could have been functioning incorrectly. Anyway, my last concern is there’s too little variables making this very unreadable for me. I’ve fixed the issues I listed in this script:
local playerGui = player:WaitForChild('PlayerGui')
local leaderboard = playerGui:WaitForChild('MainGame').Menu.LB
local function RefreshLeaderboard()
for a,b in pairs(leaderboard:GetChildren()) do
if b:IsA("Frame") then
if b.Name == player.Name then
--print("Found " .. b.Name)
local pData = ServerFunction:InvokeServer("getData", b.Name)
b.ScoreAmt.Text = pData.Points
else
print("Found " .. b.Name)
local pData = ServerFunction:InvokeServer("getData", b.Name)
b.ScoreAmt.Text = pData.Points
end
end
end
end
while wait() do
RefreshLeaderboard()
wait(1)
end
If this doesn’t change the unwanted behavior, I think you should give us the code for the ServerFunction RemoteFunction so that we can investigate what’s wrong with that.
You’re right my logic was off, and thanks for the very detailed reply, greatly appreciated. Currently, it does yield the same results as before but to follow up with you here’s my ServerFunction to get the player’s data:
ServerFunction.OnServerInvoke = function(plr)
print(plr)
local data = getData(plr)
return data
end
Edit: Forgot this:
function getData(player)
local data = DataStore["GetData"](player)
while data == nil do
data = DataStore["GetData"](player)
wait(.1)
end
return(data)
end
Edit #2: Might as well include this in my DataStore module script:
function PlayerStatManager.GetData(player)
local playerUserId = "Player_" .. player.UserId
return (sessionData[playerUserId])
end
I see what’s wrong here. The invoke function only gets the plr (player) argument. By how RemoteFunctions work, this will be the Player who invoked the RemoteFunction. This means it’s not actually using what you’re calling it with, but instead your own player. To make it work as intended, you need to add the arguments you’re actually passing it. It would be something like action, .... From what I see you might intend to use this RemoteFunction for other purposes as well, hence why the ... is there. The ellipsis is a Tuple argument which includes all the arguments passed after the last declared, meaning b.Name (the player name) will be included there. From there you can add
local args = {...}
local otherPlrName = args[1]
which uses the ellipsis argument and puts all of the arguments in there in a table, since you can’t use it normally otherwise. Now that we have the name of the player, we need to get the actual Player object from it. We could loop through all the Players in the game and see if their name matches, but I think a more elegant solution would be to use the GetPlayerByUserId method of the Players service. This would mean you’d have to either replace the names of the leaderboard frames with the players’ UserId (which I’m not sure you would want) or just add it as an attribute (or any other method of storing that information in the Frame, really. Those 2 would probably be the easiest.) in order to then get the UserId in the RefreshLeaderboard function and then pass that to the server. The server would then get the player with the Players:GetPlayerByUserId which I mentioned earlier. Overall, that would change the script to this:
ServerFunction.OnServerInvoke = function(plr, action, ...)
local args = {...}
local otherPlrId = args[1]
local otherPlr = players:GetPlayerByUserId(otherPlrId)
print(otherPlr)
local data = getData(otherPlr)
return data
end
If you need help with storing that UserId in the leaderboard frame, or still need to use player names, tell me.
I’m having a bit of trouble understanding. I understand the change to the ServerFunction, although I’m concerned about
local otherPlrId = args[1]
because is that just saying one other player? Because there’ll be at most 25 players in the game and displayed on the leaderboard. I’ll need to be able to define all other players that isn’t the player firing the remote event so that when I go to obtain the data via “getData” it will accommodate for them.
Changing the ServerFunction alone wouldn’t work, and I’m still trying to figure out how to make it work, since I would need to adjust the passed parameters in my client local script. Do I need to pass a table of players? How would I adjust this?
local pData = ServerFunction:InvokeServer("getData", b.Name)
I’m pretty sure that’s what the loop in the RefreshLeaderboard is for, isn’t it? From what I understand it loops through the player frames in the leaderboard and assigns their value. You wouldn’t need to pass all players in since the loop gets the data individually for every player.
local function RefreshLeaderboard()
for a,b in pairs(player:WaitForChild('PlayerGui'):WaitForChild('MainGame').Menu.LB:GetChildren()) do
if b:IsA("Frame") then
if b.Name == player.Name then
----print("Found " .. b.Name)
local plrUserId = Players:GetUserIdFromNameAsnyc(b.Name)
local pData = ServerFunction2:InvokeServer(plrUserId)
b.ScoreAmt.Text = pData.Points
else
print("Found " .. b.Name)
local plrUserId = Players:GetUserIdFromNameAsnyc(b.Name)
local pData = ServerFunction2:InvokeServer(plrUserId)
b.ScoreAmt.Text = pData.Points
end
end
end
end
Edit: Also noticing that I’m getting an error,
Edit #2: Wait I just realized I spelt Async wrong. Hold on let me update.
Alright so I changed Async properly, problem is now that I’m getting an error on my MainGame script:
ServerFunction2.OnServerInvoke = function(plr, action, ...)
print(plr)
local args = {...}
print(args)
local otherPlrId = args[1]
local otherPlr = Players:GetPlayerByUserId(otherPlrId)
print(otherPlr)
local data = getData(otherPlr)
return data
end