deadhead-space-freight-tycoon/apps/server/src/routes/player.ts

77 lines
3.0 KiB
TypeScript

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 { acknowledgeFinishedUpgrades, 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, clock()) };
});
// 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, now) => loadPlayerState(tx, playerId, now)),
);
registerRoute(app, routes.acknowledgeSummaries, options, async (input, { playerId }) =>
withPlayer(ctx, playerId, async (tx, now) => ({
acknowledged:
(await acknowledgeSummaries(tx, playerId, input.dispatchIds, now)) +
(await acknowledgeFinishedUpgrades(tx, playerId, input.shipIds ?? [], now)),
})),
);
registerRoute(app, routes.acceptDeal, options, async (_input, { playerId }) =>
withPlayer(ctx, playerId, async (tx, now) => {
await acceptDeal(tx, playerId, now);
return loadPlayerState(tx, playerId, now);
}),
);
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),
})),
);
}