77 lines
2.3 KiB
TypeScript
77 lines
2.3 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import { content } from "@deadhead/content";
|
|
import type { RealTime, Seed } from "@deadhead/sim";
|
|
import type { Db, Queryable } from "../db/client";
|
|
import { companies, discoveredLocations, pilots, players } from "../db/schema";
|
|
import { companyName } from "./names";
|
|
|
|
export const AVATAR_NAME = "Captain";
|
|
|
|
export interface NewPlayer {
|
|
playerId: string;
|
|
companyId: string;
|
|
avatarId: string;
|
|
}
|
|
|
|
/**
|
|
* Creates a fresh player in tutorial mode: starting credits, every location
|
|
* that starts discovered, the avatar (level 0, unassigned), and the loaner at
|
|
* Earth. The avatar's position comes from the loaner while the tutorial runs.
|
|
*/
|
|
export async function createPlayer(
|
|
db: Db,
|
|
args: { userId: string; worldSeed: Seed; now: RealTime; playerId?: string },
|
|
): Promise<NewPlayer> {
|
|
const { worldSeed, now } = args;
|
|
const playerId = args.playerId ?? randomUUID();
|
|
const companyId = randomUUID();
|
|
const avatarId = randomUUID();
|
|
|
|
await db.transaction(async (tx) => {
|
|
await tx.insert(players).values({
|
|
id: playerId,
|
|
userId: args.userId,
|
|
worldSeed,
|
|
tutorialActive: true,
|
|
loanerLocationId: "earth",
|
|
createdAt: now,
|
|
});
|
|
await createStartingState(tx, { playerId, companyId, avatarId, worldSeed, now });
|
|
});
|
|
|
|
return { playerId, companyId, avatarId };
|
|
}
|
|
|
|
/**
|
|
* A player's starting company (CR-001 tutorial state): starting credits, every
|
|
* location that starts discovered, and the avatar at Earth beside the loaner.
|
|
* Shared by guest creation and "Start a new company" (decision 0.5.74).
|
|
*/
|
|
export async function createStartingState(
|
|
tx: Queryable,
|
|
ids: { playerId: string; companyId: string; avatarId: string; worldSeed: Seed; now: RealTime },
|
|
): Promise<void> {
|
|
const { playerId, companyId, avatarId, worldSeed, now } = ids;
|
|
await tx.insert(companies).values({
|
|
id: companyId,
|
|
playerId,
|
|
name: companyName(worldSeed),
|
|
credits: content.config.startingCredits,
|
|
});
|
|
await tx
|
|
.insert(discoveredLocations)
|
|
.values(
|
|
content.locations
|
|
.filter((l) => l.startsDiscovered)
|
|
.map((l) => ({ playerId, locationId: l.id, discoveredAt: now })),
|
|
);
|
|
await tx.insert(pilots).values({
|
|
id: avatarId,
|
|
playerId,
|
|
name: AVATAR_NAME,
|
|
isAvatar: true,
|
|
locationId: "earth",
|
|
createdAt: now,
|
|
});
|
|
}
|