Create src/multiplayer/raceStandings.js
Browse files
src/multiplayer/raceStandings.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export function isRaceParticipant(player) {
|
| 2 |
+
return Boolean(player) && !player.disconnected && player.raceStatus !== 'lobby';
|
| 3 |
+
}
|
| 4 |
+
|
| 5 |
+
function getExplicitRacePosition(player) {
|
| 6 |
+
return Number.isFinite(player?.racePosition) ? player.racePosition : null;
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
function getComparableFinishTime(player) {
|
| 10 |
+
const value = player?.authoritativeFinishTime ?? player?.finishTime;
|
| 11 |
+
return Number.isFinite(value) ? value : Number.POSITIVE_INFINITY;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
export function comparePlayersByRaceStandings(a, b) {
|
| 15 |
+
const explicitPositionA = getExplicitRacePosition(a);
|
| 16 |
+
const explicitPositionB = getExplicitRacePosition(b);
|
| 17 |
+
if (explicitPositionA != null && explicitPositionB != null && explicitPositionA !== explicitPositionB) {
|
| 18 |
+
return explicitPositionA - explicitPositionB;
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
const finishedA = Boolean(a?.finished);
|
| 22 |
+
const finishedB = Boolean(b?.finished);
|
| 23 |
+
const finishTimeA = getComparableFinishTime(a);
|
| 24 |
+
const finishTimeB = getComparableFinishTime(b);
|
| 25 |
+
|
| 26 |
+
if (finishedA && finishedB && finishTimeA !== finishTimeB) return finishTimeA - finishTimeB;
|
| 27 |
+
if (finishedA !== finishedB) return finishedA ? -1 : 1;
|
| 28 |
+
|
| 29 |
+
const progressDelta = (b?.progressDistance ?? 0) - (a?.progressDistance ?? 0);
|
| 30 |
+
if (progressDelta !== 0) return progressDelta;
|
| 31 |
+
|
| 32 |
+
const lapDelta = (b?.currentLap ?? 1) - (a?.currentLap ?? 1);
|
| 33 |
+
if (lapDelta !== 0) return lapDelta;
|
| 34 |
+
|
| 35 |
+
const checkpointDelta = (b?.highestCheckpointReached ?? 0) - (a?.highestCheckpointReached ?? 0);
|
| 36 |
+
if (checkpointDelta !== 0) return checkpointDelta;
|
| 37 |
+
|
| 38 |
+
const currentCheckpointDelta = (b?.currentCheckpoint ?? 0) - (a?.currentCheckpoint ?? 0);
|
| 39 |
+
if (currentCheckpointDelta !== 0) return currentCheckpointDelta;
|
| 40 |
+
|
| 41 |
+
const joinedAtDelta = (a?.joinedAt ?? 0) - (b?.joinedAt ?? 0);
|
| 42 |
+
if (joinedAtDelta !== 0) return joinedAtDelta;
|
| 43 |
+
|
| 44 |
+
return String(a?.username ?? a?.name ?? a?.id ?? '')
|
| 45 |
+
.localeCompare(String(b?.username ?? b?.name ?? b?.id ?? ''));
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
export function getRaceStandings(players = []) {
|
| 49 |
+
return [...players]
|
| 50 |
+
.filter(isRaceParticipant)
|
| 51 |
+
.sort(comparePlayersByRaceStandings)
|
| 52 |
+
.map((player, index) => ({
|
| 53 |
+
...player,
|
| 54 |
+
racePosition: getExplicitRacePosition(player) ?? index + 1,
|
| 55 |
+
}));
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
export function getRaceStandingsMap(players = []) {
|
| 59 |
+
const standings = new Map();
|
| 60 |
+
getRaceStandings(players).forEach((player) => {
|
| 61 |
+
if (player?.id) standings.set(player.id, player.racePosition);
|
| 62 |
+
});
|
| 63 |
+
return standings;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
export function getRacePositionForPlayer(players = [], playerId) {
|
| 67 |
+
if (!playerId) return null;
|
| 68 |
+
const player = getRaceStandings(players).find((entry) => entry.id === playerId);
|
| 69 |
+
return Number.isFinite(player?.racePosition) ? player.racePosition : null;
|
| 70 |
+
}
|