Sorry I was generalizing. You are right, confusing is not the right word. They simply do not elaborate on how to sort the table with more than one variable.
I am assuming that the checkpoints are not in the straight line.
So the confusing part is that nobody explained how the sort function should look like, rather they tried to solve the issue with complicated spaghetti code or not at all.
I have a few questions, what would the loops be? Why are there two of the same functions but doing different things? What’s the difference between entryA and entryB are they like the players? Sorry if these questions may seem may seem dumb, still learning along the way.
It’s a custom sort, like as I explained earlier, you need a function to define whether it’s ascending or descending, he just made a custom sort function.
You can skip the loop code, if Your game does not use loops. (Loops as in players go around in circles)
I made a typo, as the other one should be called ChangeLoop. I edited my answer.
entryA and entryB are single entries provided to the sort function to compare. Single entry is a table with all the required player data. Such entry is created on CharacterAdded event.
This is required, since you have more than one condition to compare:
magnitude to the last checkpoint,
last used checkpoint
optional loop count, if the course circles back to the start and you want to support multi loop race.
So one the table to sort is made up of x amount of nested tables, where x is the amount of players currently in the game.
I can detect how far away the person is to the next checkpoints via magnitude, however I am unsure how proceed with detecting if two players are on the same checkpoints and which is first
Alright, but how would I implement that into the code that I already had though? How would I assign each player a position on the leaderstats, 1st, 2nd, etc?
This detects when the player hits the next checkpoint:
for _, v in pairs(workspace.Checkpoints:GetChildren()) do
v.Touched:Connect(function(hit)
if v ~= nil then
if v.Parent == workspace.Checkpoints then
local player = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent)
if player and player.Character.Humanoid.Health <= 0 then
if tonumber(v.Name) == 0 and player.leaderstats.Checkpoints.Value == game.ReplicatedStorage.RoundInfo.NumOfCheckpoints.Value then
player.leaderstats.Checkpoints.Value = 0
player.leaderstats.Laps.Value += 1
elseif tonumber(v.Name) == 0 and player.leaderstats.Laps.Value == game.ReplicatedStorage.RoundInfo.NumOfLaps.Value + 1 then
-- win
elseif player.leaderstats.Checkpoints.Value == tonumber(v.Name) - 1 then
-- gained a checkpoint
player.leaderstats.Checkpoints.Value += 1
elseif player.leaderstats.Checkpoints.Value == tonumber(v.Name) + 1 then
--wrong way
elseif player.leaderstats.Checkpoints.Value < tonumber(v.Name) - 1 then
--skipped a checkpoints
end
end
end
end
end)
end
Probably a better name lol. I changed it to laps in my code.
Please add a leaderstat value called Place, to make sure the code works. I have also switched the comparison in table sort, as the player with more loops and more checkpoints should be in the lead.
Final thought: The current code check for a magnitude to the last checkpoint, and further away player is in the lead. This is not ideal, and proper way should be to check magnitude to the NEXT checkpoint. Closest player should be in the lead. This is slightly more complicated, and you will need to sort out edge cases (for example if the current checkpoint is already last one). Something to think about.
Anyways, here goes:
-- keep all player data here
local Entries = {}
game.Players.PlayerAdded:Connect(function(plr)
-- add a new entry when character is spawned
plr.CharacterAdded:Connect(function(chr)
table.insert(Entries,{
player = plr,
root = chr.HumanoidRootPart,
checkpointRef = game.Workspace.Checkpoints:FindFirstChild("0"),
checkpointValue = 0 -- first checkpoint has a value of 0
laps = 0,
}
end)
-- remove an entry when player leaves or despawns
plr.CharacterRemoving:Connect(function()
for i, entry in pairs(Entries) do
if entry.player == plr then
table.remove(Entries,i)
break
end
end
end)
end)
-- adjust the checkpoint with this function
local function ChangeCheckpoint(plr,newCheckpoint,newCheckpointValue)
for _, entry in pairs(Entries) do
if entry.player == plr then
entry.checkpointRef = newCheckpoint
entry.checkpointValue = newCheckpointValue
break
end
end
end
-- adjust the laps with this function
--EDIT: Corrected the name of the function
local function ChangeLaps(plr,newLapsValue)
for _, entry in pairs(Entries) do
if entry.player == plr then
entry.laps = newLapsValue
break
end
end
end
for _, v in pairs(workspace.Checkpoints:GetChildren()) do
v.Touched:Connect(function(hit)
-- if v ~= nil then -- this check is redundant
-- if v.Parent == workspace.Checkpoints then -- this check is redundant
local player = game:GetService("Players"):GetPlayerFromCharacter(hit.Parent)
if player and player.Character.Humanoid.Health <= 0 then -- why? is the character supposed to be dead?
if tonumber(v.Name) == 0 and player.leaderstats.Checkpoints.Value == game.ReplicatedStorage.RoundInfo.NumOfCheckpoints.Value then
player.leaderstats.Checkpoints.Value = 0
player.leaderstats.Laps.Value += 1
--update checkpoint here
ChangeCheckpoint(player,v,0)
--update laps
ChangeLaps(player,player.leaderstats.Laps.Value)
elseif tonumber(v.Name) == 0 and player.leaderstats.Laps.Value == game.ReplicatedStorage.RoundInfo.NumOfLaps.Value + 1 then
-- win
elseif player.leaderstats.Checkpoints.Value == tonumber(v.Name) - 1 then
-- gained a checkpoint
player.leaderstats.Checkpoints.Value += 1
--update checkpoint here too
ChangeCheckpoint(player,v,player.leaderstats.Checkpoints.Value)
elseif player.leaderstats.Checkpoints.Value == tonumber(v.Name) + 1 then
--wrong way
elseif player.leaderstats.Checkpoints.Value < tonumber(v.Name) - 1 then
--skipped a checkpoints
end
end
--end
--end
end)
end
-- finally the sort function
local function CustomSort(entryA, entryB)
--first check the laps
if entryA.laps ~= entryB.laps then
-- players have different amount of laps, return the result
return entryA.laps > entryB.laps
-- if loop is the same, check if the checkpoints are different.
elseif entryA.checkpointValue ~= entryB.checkpointValue then
-- players have different checkpoints, return the result (each checkpoint has a different value)
return entryA.checkpointValue > entryB.checkpointValue
else
-- finally if checkpoints are the same, see which player is further away from said checkpoint
return (entryA.root.Position - entryA.checkpointRef.Position).Magnitude >
(entryB.root.Position - entryA.checkpointRef.Position).Magnitude
end
end
-- Example of sorting loop each second (adjust if needed)
task.defer(function()
while true do
task.wait(1) -- check every second
table.sort(Entries,CustomSort)
--update leaderstats
for place, entry in pairs(Entries) do
entry.player.leaderstats.Place.Value = place
end
end
end)
Creates a coroutine. This is similar to task.spawn() and coroutine.wrap(), but does not force the thread to be run immediately. I prefer it to other methods to avoid inexplicable lag spikes.
Of course sometimes task.spawn() is better, for example projectile handlers or timers.
If you are unfamiliar with coroutines, I am afraid I have no time to explain it today, but I am sure you can find plenty of tutorial here on forum.
Long story short, when you create coroutine, you are like creating separate almost independent script, that runs along side the main script.