135 lines
4.8 KiB
TypeScript
135 lines
4.8 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import type { PlayerState } from "@deadhead/schema";
|
|
import type { RealTime } from "@deadhead/sim";
|
|
import { and, eq, isNull, max } from "drizzle-orm";
|
|
import type { Queryable } from "../db/client";
|
|
import {
|
|
bays,
|
|
companies,
|
|
discoveredLocations,
|
|
dispatches,
|
|
pilots,
|
|
players,
|
|
ships,
|
|
} from "../db/schema";
|
|
import { isDealAvailable } from "./deal";
|
|
import { goalStates } from "./goals";
|
|
import { toApiDispatch, toApiPilot, toApiShip } from "./mappers";
|
|
import { loadUpgradeLevels } from "./upgrades";
|
|
|
|
/** Reads everything the client needs about one player. */
|
|
export async function loadPlayerState(
|
|
db: Queryable,
|
|
playerId: string,
|
|
now: RealTime,
|
|
): Promise<PlayerState> {
|
|
const [player] = await db.select().from(players).where(eq(players.id, playerId));
|
|
const [company] = await db.select().from(companies).where(eq(companies.playerId, playerId));
|
|
if (!player || !company) throw new Error(`Player ${playerId} not found`);
|
|
|
|
const shipRows = await db.select().from(ships).where(eq(ships.playerId, playerId));
|
|
const pilotRows = await db.select().from(pilots).where(eq(pilots.playerId, playerId));
|
|
const bayRows = await db.select().from(bays).where(eq(bays.playerId, playerId));
|
|
const discovered = await db
|
|
.select({ locationId: discoveredLocations.locationId })
|
|
.from(discoveredLocations)
|
|
.where(eq(discoveredLocations.playerId, playerId));
|
|
const active = await db
|
|
.select()
|
|
.from(dispatches)
|
|
.where(and(eq(dispatches.playerId, playerId), eq(dispatches.status, "in_flight")));
|
|
const arrivals = await db
|
|
.select({ shipId: dispatches.shipId, at: max(dispatches.arrivalAt) })
|
|
.from(dispatches)
|
|
.where(and(eq(dispatches.playerId, playerId), eq(dispatches.status, "resolved")))
|
|
.groupBy(dispatches.shipId);
|
|
const unacknowledged = await db
|
|
.select()
|
|
.from(dispatches)
|
|
.where(
|
|
and(
|
|
eq(dispatches.playerId, playerId),
|
|
eq(dispatches.status, "resolved"),
|
|
isNull(dispatches.acknowledgedAt),
|
|
),
|
|
);
|
|
|
|
const discoveredIds = new Set(discovered.map((d) => d.locationId));
|
|
const shipById = new Map(shipRows.map((s) => [s.id, s]));
|
|
const activeByShip = new Map(active.filter((d) => d.shipId).map((d) => [d.shipId!, d.id]));
|
|
const upgradesByShip = await loadUpgradeLevels(
|
|
db,
|
|
shipRows.map((s) => s.id),
|
|
);
|
|
const lastArrivalByShip = new Map(arrivals.map((a) => [a.shipId, a.at]));
|
|
const pilotByShip = new Map(
|
|
pilotRows.filter((p) => p.assignedShipId).map((p) => [p.assignedShipId!, p.id]),
|
|
);
|
|
|
|
return {
|
|
playerId,
|
|
company: {
|
|
id: company.id,
|
|
name: company.name,
|
|
credits: company.credits,
|
|
loanBalance: company.loanBalance,
|
|
},
|
|
ships: shipRows.map((s) =>
|
|
toApiShip(
|
|
s,
|
|
{
|
|
dispatchId: activeByShip.get(s.id) ?? null,
|
|
pilotId: pilotByShip.get(s.id) ?? null,
|
|
lastArrivalAt: lastArrivalByShip.get(s.id) ?? s.createdAt,
|
|
upgrades: upgradesByShip.get(s.id) ?? {},
|
|
},
|
|
now,
|
|
),
|
|
),
|
|
pilots: pilotRows.map((p) =>
|
|
toApiPilot(
|
|
p,
|
|
p.assignedShipId ? (shipById.get(p.assignedShipId)?.locationId ?? null) : null,
|
|
now,
|
|
),
|
|
),
|
|
bays: bayRows.map((b) => ({ id: b.id, locationId: b.locationId })),
|
|
locations: content.locations.map((l) => ({ id: l.id, discovered: discoveredIds.has(l.id) })),
|
|
tutorial:
|
|
player.tutorialActive && player.loanerLocationId
|
|
? {
|
|
loanerLocationId: player.loanerLocationId as "earth" | "moon",
|
|
runInFlight: active.some((d) => d.tutorial),
|
|
runsCompleted: player.tutorialRunsCompleted,
|
|
dealAvailable: isDealAvailable(
|
|
player,
|
|
active.some((d) => d.tutorial),
|
|
),
|
|
}
|
|
: null,
|
|
activeDispatches: active.map(toApiDispatch),
|
|
summaries: unacknowledged.map((d) => ({
|
|
dispatchId: d.id,
|
|
shipId: d.record.shipId,
|
|
pilotId: d.pilotId,
|
|
originId: d.record.originId,
|
|
destinationId: d.record.destinationId,
|
|
tutorial: d.tutorial,
|
|
deadhead: d.record.contract === null,
|
|
fuelCost: d.record.fuelCost,
|
|
goodId: d.record.contract?.goodId ?? null,
|
|
massTonnes: d.record.contract?.massTonnes ?? null,
|
|
shipper: d.record.contract?.shipper ?? null,
|
|
credits: d.resolution?.credits ?? 0,
|
|
loanRepayment: d.resolution?.loanRepayment ?? 0,
|
|
pilotPay: d.resolution?.pilotPay ?? 0,
|
|
pilotXp: d.resolution?.pilotXp ?? 0,
|
|
discoveredLocationId: d.resolution?.discoversDestination ? d.record.destinationId : null,
|
|
scriptedId: d.record.contract?.scriptedId ?? null,
|
|
restEndsAt: d.resolution?.pilotRestEndsAt ?? null,
|
|
resolvedAt: d.resolvedAt ?? d.arrivalAt,
|
|
})),
|
|
goals: await goalStates(db, playerId),
|
|
};
|
|
}
|