Release v0.1.20 2026-07-19
This commit is contained in:
@@ -651,6 +651,7 @@ export function createGameApiHandler(options = {}) {
|
||||
match: {
|
||||
id: match.id,
|
||||
seed: match.seed,
|
||||
generation: match.generation,
|
||||
countdownEndsAtMs: match.countdownEndsAtMs,
|
||||
opponentName: opponent.hunterName,
|
||||
role: ticket.side,
|
||||
@@ -694,10 +695,12 @@ export function createGameApiHandler(options = {}) {
|
||||
const match = {
|
||||
id: matchId,
|
||||
seed: randomBytes(4).readUInt32BE(0) || 1,
|
||||
generation: 1,
|
||||
createdAt: now,
|
||||
countdownEndsAtMs: now + HOCKEY_PVP_COUNTDOWN_MS,
|
||||
players: { host: opponent, guest: ticket },
|
||||
snapshots: { host: null, guest: null },
|
||||
rematch: null,
|
||||
};
|
||||
opponent.matchId = matchId;
|
||||
opponent.side = "host";
|
||||
@@ -725,6 +728,72 @@ export function createGameApiHandler(options = {}) {
|
||||
return { match, side };
|
||||
}
|
||||
|
||||
function hockeyPvpRematchResult(match, side, requestedGeneration) {
|
||||
const rematch = match.rematch;
|
||||
if (!rematch || rematch.fromGeneration !== requestedGeneration || !rematch.ready) {
|
||||
return { status: "waiting" };
|
||||
}
|
||||
const opponentSide = side === "host" ? "guest" : "host";
|
||||
return {
|
||||
status: "matched",
|
||||
match: {
|
||||
id: match.id,
|
||||
seed: rematch.seed,
|
||||
generation: rematch.toGeneration,
|
||||
countdownEndsAtMs: rematch.countdownEndsAtMs,
|
||||
opponentName: match.players[opponentSide].hunterName,
|
||||
role: side,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function requestHockeyPvpRematch(session, matchId, payload) {
|
||||
const { match, side } = requireHockeyPvpMatch(session, matchId);
|
||||
const generation = Number(payload?.generation);
|
||||
if (!Number.isSafeInteger(generation) || generation < 1) throw apiError("PVP match generation is invalid.");
|
||||
|
||||
if (generation < match.generation) {
|
||||
if (match.rematch?.fromGeneration !== generation || !match.rematch.ready) {
|
||||
throw apiError("PVP match generation is stale.", 409);
|
||||
}
|
||||
return hockeyPvpRematchResult(match, side, generation);
|
||||
}
|
||||
if (generation > match.generation) throw apiError("PVP match generation is invalid.", 409);
|
||||
|
||||
if (!match.rematch || match.rematch.fromGeneration !== generation) {
|
||||
match.rematch = {
|
||||
fromGeneration: generation,
|
||||
toGeneration: generation + 1,
|
||||
requested: { host: false, guest: false },
|
||||
ready: false,
|
||||
seed: 0,
|
||||
countdownEndsAtMs: 0,
|
||||
};
|
||||
}
|
||||
match.rematch.requested[side] = true;
|
||||
if (!match.rematch.ready && match.rematch.requested.host && match.rematch.requested.guest) {
|
||||
const now = Date.now();
|
||||
match.rematch.ready = true;
|
||||
match.rematch.seed = randomBytes(4).readUInt32BE(0) || 1;
|
||||
match.rematch.countdownEndsAtMs = now + HOCKEY_PVP_COUNTDOWN_MS;
|
||||
match.seed = match.rematch.seed;
|
||||
match.generation = match.rematch.toGeneration;
|
||||
match.countdownEndsAtMs = match.rematch.countdownEndsAtMs;
|
||||
match.snapshots = { host: null, guest: null };
|
||||
}
|
||||
return hockeyPvpRematchResult(match, side, generation);
|
||||
}
|
||||
|
||||
function cancelHockeyPvpRematch(session, matchId, payload) {
|
||||
const { match, side } = requireHockeyPvpMatch(session, matchId);
|
||||
const generation = Number(payload?.generation);
|
||||
if (!Number.isSafeInteger(generation) || generation < 1) throw apiError("PVP match generation is invalid.");
|
||||
if (match.rematch?.fromGeneration === generation && !match.rematch.ready) {
|
||||
match.rematch.requested[side] = false;
|
||||
}
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
async function handle(request, response, next) {
|
||||
if (!request.url?.startsWith("/api/")) return next();
|
||||
setCorsHeaders(request, response);
|
||||
@@ -779,6 +848,7 @@ export function createGameApiHandler(options = {}) {
|
||||
if (pvpStateMatch && request.method === "PUT") {
|
||||
const { match, side } = requireHockeyPvpMatch(session, pvpStateMatch[1]);
|
||||
const payload = await readJson(request);
|
||||
if (payload?.generation !== match.generation) throw apiError("PVP match generation is stale.", 409);
|
||||
if (!payload?.snapshot || typeof payload.snapshot !== "object") throw apiError("PVP snapshot is invalid.");
|
||||
match.snapshots[side] = payload.snapshot;
|
||||
return sendJson(response, 200, {
|
||||
@@ -786,6 +856,13 @@ export function createGameApiHandler(options = {}) {
|
||||
hostSnapshot: match.snapshots.host,
|
||||
});
|
||||
}
|
||||
const pvpRematchMatch = path.match(/^\/api\/hockey-pvp\/matches\/([A-Za-z0-9_-]+)\/rematch$/);
|
||||
if (pvpRematchMatch && request.method === "POST") {
|
||||
return sendJson(response, 200, requestHockeyPvpRematch(session, pvpRematchMatch[1], await readJson(request)));
|
||||
}
|
||||
if (pvpRematchMatch && request.method === "DELETE") {
|
||||
return sendJson(response, 200, cancelHockeyPvpRematch(session, pvpRematchMatch[1], await readJson(request)));
|
||||
}
|
||||
if (path === "/api/saves" && request.method === "GET") {
|
||||
return sendJson(response, 200, { slots: listSaves(database, session.accountId) });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user