import { randomInt } from "node:crypto"; import { content } from "@deadhead/content"; import { routes } from "@deadhead/schema"; import { realToGame } from "@deadhead/sim"; import { fromNodeHeaders } from "better-auth/node"; import type { FastifyInstance } from "fastify"; import { acceptDeal } from "../game/deal"; import { claimGoal } from "../game/goals"; import { repayLoan } from "../game/loan"; import { createPlayer } from "../game/new-player"; import { loadPlayerState } from "../game/player-state"; import { acknowledgeSummaries } from "../game/summaries"; import { ApiError } from "../http/errors"; import { registerRoute } from "../http/route"; import type { AppContext } from "./context"; import { withPlayer } from "./with-player"; /** Server time, guest sign-up and player state. */ export function registerPlayerRoutes(app: FastifyInstance, ctx: AppContext): void { const { db, auth, clock, options } = ctx; registerRoute(app, routes.getServerTime, options, async () => { const realTime = clock(); return { realTime, gameTime: realToGame(realTime, content.config) }; }); registerRoute(app, routes.createGuest, options, async (_input, { request, reply }) => { const signIn = await auth.api.signInAnonymous({ headers: fromNodeHeaders(request.headers), returnHeaders: true, }); if (!signIn.response) throw new ApiError(500, "internal-error", "Could not create a guest."); const { playerId } = await createPlayer(db, { userId: signIn.response.user.id, // A 32-bit unsigned seed (decision 0.5.10). worldSeed: randomInt(0, 2 ** 32), now: clock(), }); // Pass Better Auth's session cookie through to the client. reply.header("set-cookie", signIn.headers.getSetCookie()); return { playerId, state: await loadPlayerState(db, playerId) }; }); // Lazy resolution on read (Step 6.1): apply every arrival before returning state. registerRoute(app, routes.getPlayerState, options, async (_input, { playerId }) => withPlayer(ctx, playerId, (tx) => loadPlayerState(tx, playerId)), ); registerRoute(app, routes.acknowledgeSummaries, options, async (input, { playerId }) => withPlayer(ctx, playerId, async (tx, now) => ({ acknowledged: await acknowledgeSummaries(tx, playerId, input.dispatchIds, now), })), ); registerRoute(app, routes.acceptDeal, options, async (_input, { playerId }) => withPlayer(ctx, playerId, async (tx, now) => { await acceptDeal(tx, playerId, now); return loadPlayerState(tx, playerId); }), ); registerRoute(app, routes.repayLoan, options, async (input, { playerId }) => withPlayer(ctx, playerId, async (tx) => ({ company: await repayLoan(tx, playerId, input.amount), })), ); registerRoute(app, routes.claimGoal, options, async (input, { playerId }) => withPlayer(ctx, playerId, async (tx, now) => ({ company: await claimGoal(tx, playerId, input.goalId, now), })), ); }