diff --git a/components/leaderboards/top-leaderboards-table.tsx b/components/leaderboards/top-leaderboards-table.tsx
index 271bfe2f..0effa17e 100644
--- a/components/leaderboards/top-leaderboards-table.tsx
+++ b/components/leaderboards/top-leaderboards-table.tsx
@@ -34,6 +34,24 @@ const TopLeaderboardsTable = ({ leaderBoardData, loading, error }: Props) => {
accessor: "rank",
textAlignment: "center",
},
+ {
+ title: "RC",
+ accessor: "change",
+ textAlignment: "center",
+ render: ({ change }: { change: number | string }) => {
+ if (typeof change === "string") {
+ return change;
+ } else {
+ return change > 0 ? (
+ +{change}
+ ) : change < 0 ? (
+ {change}
+ ) : (
+ <>>
+ );
+ }
+ },
+ },
{
title: "ELO",
accessor: "rating",
@@ -118,7 +136,7 @@ const TopLeaderboardsTable = ({ leaderBoardData, loading, error }: Props) => {
accessor: "lastmatchdate",
title: "Last Game",
textAlignment: "right",
- width: 125,
+ width: 110,
// @ts-ignore
render: ({ lastmatchdate }) => {
return ;
diff --git a/config.ts b/config.ts
index 6410c6e9..0f7408f5 100644
--- a/config.ts
+++ b/config.ts
@@ -301,6 +301,7 @@ const config = {
DONATION_LINK: "https://ko-fi.com/cohstats",
GITHUB_LINK: repository.url,
CDN_ASSETS_HOSTING: "https://cdn.coh3stats.com",
+ STORAGE_LINK: "https://storage.coh3stats.com",
BASE_CLOUD_FUNCTIONS_URL: useFirebaseEmulators
? firebaseFunctions.EMULATORS_URL
: firebaseFunctions.CLOUD_URL,
diff --git a/src/apis/coh3stats-api.ts b/src/apis/coh3stats-api.ts
index 82281e6c..f1657a15 100644
--- a/src/apis/coh3stats-api.ts
+++ b/src/apis/coh3stats-api.ts
@@ -1,7 +1,9 @@
import config from "../../config";
import {
GlobalAchievementsData,
+ leaderBoardType,
ProcessedMatch,
+ raceType,
TwitchStream,
YouTubeVideo,
} from "../coh3/coh3-types";
@@ -293,6 +295,73 @@ const triggerPlayerNemesisAliasesUpdate = async (playerID: string | number) => {
return await response.json();
};
+const _fetchFirst10LinesOfLeaderBoards = async (url: string) => {
+ const response = await fetch(url);
+ const reader = response.body?.getReader();
+ const decoder = new TextDecoder("utf-8");
+
+ let buffer = "";
+ let lines: string[] = [];
+
+ while (lines.length < 10 && reader) {
+ const { value, done } = await reader.read();
+ if (done) break;
+
+ // Decode and append to buffer
+ buffer += decoder.decode(value || new Uint8Array(), { stream: true });
+
+ // Split the buffer into lines
+ const splitBuffer = buffer.split("},");
+
+ // Save the last part of the current buffer for the next iteration
+ buffer = splitBuffer.pop() || "";
+
+ // Add the complete lines to our lines array
+ lines.push(...splitBuffer);
+
+ // If we have more than 10 lines, truncate the array
+ if (lines.length >= 10) {
+ lines = lines.slice(0, 10);
+ break;
+ }
+ }
+
+ // Remove the starts of the file
+ lines[0] = lines[0].replace('{"leaderboards":[', "");
+
+ return lines.reduce((acc: Record, line) => {
+ // Add the missing closing bracket and parse the JSON
+ const obj = JSON.parse(line + "}");
+
+ // Use the statgroup_id as the key
+ acc[obj.statgroup_id] = obj;
+
+ return acc;
+ }, {});
+};
+
+/**
+ * Fetches only top 10 items for now
+ * https://storage.coh3stats.com/leaderboards/1718064000/1718064000_1v1_american.json
+ * @param timeStamp
+ * @param type
+ * @param race
+ */
+const getOldLeaderboardData = async (
+ timeStamp: string | number,
+ type: leaderBoardType,
+ race: raceType,
+) => {
+ const url = `${config.STORAGE_LINK}/leaderboards/${timeStamp}/${timeStamp}_${type}_${race}.json`;
+
+ try {
+ return _fetchFirst10LinesOfLeaderBoards(url);
+ } catch (e) {
+ console.error(e);
+ return {};
+ }
+};
+
export {
getPlayerCardInfo,
getPlayerRecentMatches,
@@ -304,4 +373,5 @@ export {
getPlayersCardsConfigsHttp,
getYouTubeVideosHttp,
triggerPlayerNemesisAliasesUpdate,
+ getOldLeaderboardData,
};
diff --git a/src/leaderboards/top-leaderboards.ts b/src/leaderboards/top-leaderboards.ts
index f855926a..51267ef5 100644
--- a/src/leaderboards/top-leaderboards.ts
+++ b/src/leaderboards/top-leaderboards.ts
@@ -1,10 +1,33 @@
import { getLeaderBoardData } from "../coh3/coh3-api";
import { findAndMergeStatGroups } from "../coh3/helpers";
import { raceType, Top1v1LeaderboardsData } from "../coh3/coh3-types";
+import { getOldLeaderboardData } from "../apis/coh3stats-api";
const getTop1v1LeaderBoards = async (race: raceType): Promise => {
- const leaderBoardDataRaw = await getLeaderBoardData(race, "1v1", 1, 10, 1, "steam");
- const leaderBoardData = findAndMergeStatGroups(leaderBoardDataRaw, null);
+ const date = new Date();
+ const yesterdayTimeStamp =
+ Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate() - 1, 0, 0, 0) / 1000;
+
+ const promisedLeaderBoardDataRaw = getLeaderBoardData(race, "1v1", 1, 10, 1, "steam");
+ const promisedOldData = getOldLeaderboardData(yesterdayTimeStamp, "1v1", race);
+
+ // Old data is already well formatted
+ const [leaderBoardDataRaw, oldData] = await Promise.all([
+ promisedLeaderBoardDataRaw,
+ promisedOldData,
+ ]);
+ // We need to convert the RAW data
+ let leaderBoardData = findAndMergeStatGroups(leaderBoardDataRaw, null);
+
+ // Add the change there
+ leaderBoardData = leaderBoardData.map((value) => {
+ if (oldData[value.statgroup_id]) {
+ value.change = value.rank - oldData[value.statgroup_id].rank;
+ } else {
+ value.change = "new";
+ }
+ return value;
+ });
return {
race: race,