Release v0.1.7 2026-07-12
This commit is contained in:
@@ -217,6 +217,14 @@ function syncLeaderboardStats(database, accountId, slotId, save) {
|
||||
highest_round = excluded.highest_round,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
`).run(accountId, slotId, highestRound);
|
||||
const highestEndlessKills = normalizeNonNegativeInteger(save.stats?.highestRogueTrialsEndlessKills);
|
||||
database.prepare(`
|
||||
INSERT INTO rogue_trials_endless_records (account_id, slot_id, highest_boss_kills, updated_at)
|
||||
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT(account_id, slot_id) DO UPDATE SET
|
||||
highest_boss_kills = excluded.highest_boss_kills,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
`).run(accountId, slotId, highestEndlessKills);
|
||||
}
|
||||
|
||||
function writeSave(database, accountId, slotId, rawSave) {
|
||||
@@ -324,6 +332,32 @@ function roguelikeLeaderboard(database, accountId, slotId) {
|
||||
};
|
||||
}
|
||||
|
||||
function rogueTrialsEndlessLeaderboard(database, accountId, slotId) {
|
||||
const rows = database.prepare(`
|
||||
WITH ranked AS (
|
||||
SELECT
|
||||
RANK() OVER (ORDER BY records.highest_boss_kills DESC) AS rank,
|
||||
records.account_id AS accountId,
|
||||
records.slot_id AS slotId,
|
||||
records.highest_boss_kills AS highestBossKills,
|
||||
accounts.username,
|
||||
saves.hunter_name AS hunterName,
|
||||
records.updated_at AS updatedAt
|
||||
FROM rogue_trials_endless_records records
|
||||
JOIN accounts ON accounts.id = records.account_id
|
||||
JOIN hunter_saves saves ON saves.account_id = records.account_id AND saves.slot_id = records.slot_id
|
||||
WHERE records.highest_boss_kills > 0
|
||||
)
|
||||
SELECT * FROM ranked ORDER BY highestBossKills DESC, updatedAt ASC, accountId ASC, slotId ASC
|
||||
`).all();
|
||||
const current = rows.find((row) => row.accountId === accountId && row.slotId === slotId) ?? null;
|
||||
return {
|
||||
kind: "rogue-trials-endless",
|
||||
top: rows.slice(0, 5).map((row) => leaderboardEntry(row, "highestBossKills")),
|
||||
current: current ? leaderboardEntry(current, "highestBossKills") : null,
|
||||
};
|
||||
}
|
||||
|
||||
export function createGameApiHandler(options = {}) {
|
||||
const dataDirectory = resolve(options.dataDirectory ?? process.env.DATA_DIR ?? "data");
|
||||
mkdirSync(dataDirectory, { recursive: true });
|
||||
@@ -387,6 +421,10 @@ export function createGameApiHandler(options = {}) {
|
||||
const slotId = validateSlotId(url.searchParams.get("slot"));
|
||||
return sendJson(response, 200, roguelikeLeaderboard(database, session.accountId, slotId));
|
||||
}
|
||||
if (path === "/api/leaderboards/rogue-trials-endless" && request.method === "GET") {
|
||||
const slotId = validateSlotId(url.searchParams.get("slot"));
|
||||
return sendJson(response, 200, rogueTrialsEndlessLeaderboard(database, session.accountId, slotId));
|
||||
}
|
||||
return sendJson(response, 404, { error: "API route not found." });
|
||||
} catch (error) {
|
||||
const status = Number(error?.status) || 500;
|
||||
|
||||
@@ -34,12 +34,12 @@ async function json(path, init = {}) {
|
||||
return { response, body };
|
||||
}
|
||||
|
||||
function save(slotId, hunterName, bulldromeKills, highestRoguelikeRound) {
|
||||
function save(slotId, hunterName, bulldromeKills, highestRoguelikeRound, highestRogueTrialsEndlessKills) {
|
||||
return {
|
||||
schemaVersion: 5,
|
||||
slotId,
|
||||
hunterName,
|
||||
stats: { bossKills: { bulldrome: bulldromeKills }, highestRoguelikeRound },
|
||||
stats: { bossKills: { bulldrome: bulldromeKills }, highestRoguelikeRound, highestRogueTrialsEndlessKills },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,13 +62,14 @@ test("accounts, server saves, and top-five plus current rankings work end to end
|
||||
const token = registration.body.token;
|
||||
const kills = 60 - index * 10;
|
||||
const highestRound = 30 - index * 4;
|
||||
const highestEndlessKills = 24 - index * 3;
|
||||
const upload = await json("/api/saves/1", {
|
||||
method: "PUT",
|
||||
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ save: save(1, `Hero ${index}`, kills, highestRound) }),
|
||||
body: JSON.stringify({ save: save(1, `Hero ${index}`, kills, highestRound, highestEndlessKills) }),
|
||||
});
|
||||
assert.equal(upload.response.status, 200);
|
||||
players.push({ token, kills, highestRound });
|
||||
players.push({ token, kills, highestRound, highestEndlessKills });
|
||||
}
|
||||
|
||||
const current = players[5];
|
||||
@@ -87,6 +88,15 @@ test("accounts, server saves, and top-five plus current rankings work end to end
|
||||
assert.equal(rogueBoard.body.current.rank, 6);
|
||||
assert.equal(rogueBoard.body.current.value, current.highestRound);
|
||||
|
||||
const endlessBoard = await json("/api/leaderboards/rogue-trials-endless?slot=1", {
|
||||
headers: { Authorization: `Bearer ${current.token}` },
|
||||
});
|
||||
assert.equal(endlessBoard.body.kind, "rogue-trials-endless");
|
||||
assert.equal(endlessBoard.body.top.length, 5);
|
||||
assert.equal(endlessBoard.body.top[0].value, 24);
|
||||
assert.equal(endlessBoard.body.current.rank, 6);
|
||||
assert.equal(endlessBoard.body.current.value, current.highestEndlessKills);
|
||||
|
||||
const download = await json("/api/saves/1", {
|
||||
headers: { Authorization: `Bearer ${current.token}` },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user