176 lines
6.2 KiB
TypeScript
176 lines
6.2 KiB
TypeScript
import { z } from "zod";
|
|
|
|
/**
|
|
* Per-player data sent over the API. Static data (location details, ship
|
|
* models, cargo, skills, config) comes from `@deadhead/content`; these shapes
|
|
* refer to it by id. Times are Unix milliseconds, as in `@deadhead/sim`.
|
|
*/
|
|
|
|
export const idSchema = z.string().min(1);
|
|
export const timeSchema = z.number().int().nonnegative();
|
|
const credits = z.number().int();
|
|
|
|
export const serverTimeSchema = z.object({
|
|
realTime: timeSchema,
|
|
/** In-game instant (sim `realToGame` of `realTime`). */
|
|
gameTime: z.number().int(),
|
|
});
|
|
export type ServerTime = z.infer<typeof serverTimeSchema>;
|
|
|
|
export const companySchema = z.object({
|
|
id: idSchema,
|
|
name: z.string().min(1),
|
|
/** May go negative only through pilot pay on a low payout (see DECISIONS Step 3.17). */
|
|
credits,
|
|
/** Owed to the lender from the deal (decision 0.5.32); 0 when paid off or never taken. */
|
|
loanBalance: credits.nonnegative(),
|
|
});
|
|
export type Company = z.infer<typeof companySchema>;
|
|
|
|
export const shipSchema = z.object({
|
|
id: idSchema,
|
|
modelId: idSchema,
|
|
name: z.string().min(1),
|
|
status: z.enum(["idle", "in-flight"]),
|
|
/** Where an idle ship is; null while in flight. */
|
|
locationId: idSchema.nullable(),
|
|
/** The active dispatch while in flight; null when idle. */
|
|
dispatchId: idSchema.nullable(),
|
|
pilotId: idSchema.nullable(),
|
|
/**
|
|
* When the ship last arrived somewhere: its latest resolved flight, or its
|
|
* delivery if it has not flown. Picks the default Contracts location (CR-002).
|
|
*/
|
|
lastArrivalAt: timeSchema,
|
|
});
|
|
export type Ship = z.infer<typeof shipSchema>;
|
|
|
|
export const pilotSchema = z.object({
|
|
id: idSchema,
|
|
name: z.string().min(1),
|
|
isAvatar: z.boolean(),
|
|
xp: z.number().int().nonnegative(),
|
|
level: z.number().int().nonnegative(),
|
|
/** Skill levels by skill id; a missing skill is level 0. */
|
|
skills: z.record(z.string(), z.number().int().nonnegative()),
|
|
skillPointsAvailable: z.number().int().nonnegative(),
|
|
assignedShipId: idSchema.nullable(),
|
|
status: z.enum(["idle", "in-flight"]),
|
|
/** Where the pilot is (their ship's location if assigned); null while in flight. */
|
|
locationId: idSchema.nullable(),
|
|
/** Game hours flown since the last rest, and the limit before mandatory rest (CR-004). */
|
|
dutyHours: z.number().nonnegative(),
|
|
dutyLimit: z.number().positive(),
|
|
/** Resting pilots cannot fly until `restEndsAt` (null when not resting). */
|
|
resting: z.boolean(),
|
|
restEndsAt: timeSchema.nullable(),
|
|
});
|
|
export type Pilot = z.infer<typeof pilotSchema>;
|
|
|
|
export const baySchema = z.object({
|
|
id: idSchema,
|
|
locationId: idSchema,
|
|
});
|
|
export type Bay = z.infer<typeof baySchema>;
|
|
|
|
export const locationStateSchema = z.object({
|
|
id: idSchema,
|
|
discovered: z.boolean(),
|
|
});
|
|
export type LocationState = z.infer<typeof locationStateSchema>;
|
|
|
|
/** Mirrors the sim `Contract`. */
|
|
export const contractSchema = z.object({
|
|
id: idSchema,
|
|
originId: idSchema,
|
|
destinationId: idSchema,
|
|
cargoTypeId: idSchema,
|
|
tier: z.number().int().min(0),
|
|
massTonnes: z.number().positive(),
|
|
payout: credits,
|
|
requiredSkill: z.object({ skillId: idSchema, level: z.number().int().positive() }).nullable(),
|
|
requiredLongHaulLevel: z.number().int().nonnegative(),
|
|
offeredAt: timeSchema,
|
|
expiresAt: timeSchema,
|
|
firstContact: z.boolean(),
|
|
/** Set on a scripted tutorial contract (decision 0.5.33). */
|
|
scriptedId: idSchema.optional(),
|
|
});
|
|
export type Contract = z.infer<typeof contractSchema>;
|
|
|
|
/** Mirrors the sim `DispatchRecord`, plus server-side status. */
|
|
export const dispatchSchema = z.object({
|
|
id: idSchema,
|
|
shipId: idSchema,
|
|
pilotId: idSchema,
|
|
contract: contractSchema.nullable(),
|
|
originId: idSchema,
|
|
destinationId: idSchema,
|
|
tutorial: z.boolean(),
|
|
departureAt: timeSchema,
|
|
arrivalAt: timeSchema,
|
|
distanceMillionKm: z.number().nonnegative(),
|
|
fuelCost: credits.nonnegative(),
|
|
expectedPayout: credits,
|
|
seed: z.number().int().nonnegative(),
|
|
status: z.enum(["in-flight", "resolved"]),
|
|
resolvedAt: timeSchema.nullable(),
|
|
});
|
|
export type Dispatch = z.infer<typeof dispatchSchema>;
|
|
|
|
/** One "while you were away" entry: a resolved dispatch not yet acknowledged. */
|
|
export const resolutionSummarySchema = z.object({
|
|
dispatchId: idSchema,
|
|
shipId: idSchema,
|
|
pilotId: idSchema,
|
|
originId: idSchema,
|
|
destinationId: idSchema,
|
|
tutorial: z.boolean(),
|
|
/** Credits added after pilot pay and loan repayment. */
|
|
credits,
|
|
/** Credits the lender took from this payout (decision 0.5.32). */
|
|
loanRepayment: credits.nonnegative(),
|
|
pilotXp: z.number().int().nonnegative(),
|
|
/** Set when the delivery discovered its destination. */
|
|
discoveredLocationId: idSchema.nullable(),
|
|
/** The scripted tutorial contract delivered, if any (for its flavor message). */
|
|
scriptedId: idSchema.nullable(),
|
|
/** Set when the trip ended in mandatory rest: the pilot rests until then (CR-004). */
|
|
restEndsAt: timeSchema.nullable(),
|
|
resolvedAt: timeSchema,
|
|
});
|
|
export type ResolutionSummary = z.infer<typeof resolutionSummarySchema>;
|
|
|
|
/** Decision 0.5.26: the loaner is not in the ships list. Null once the tutorial ends. */
|
|
export const tutorialStateSchema = z.object({
|
|
loanerLocationId: z.enum(["earth", "moon"]),
|
|
runInFlight: z.boolean(),
|
|
runsCompleted: z.number().int().nonnegative(),
|
|
/** The one-time deal can be accepted now (decision 0.5.30). */
|
|
dealAvailable: z.boolean(),
|
|
});
|
|
export type TutorialState = z.infer<typeof tutorialStateSchema>;
|
|
|
|
/** An onboarding goal's progress (decision 0.5.34). Static details come from content. */
|
|
export const goalStateSchema = z.object({
|
|
id: idSchema,
|
|
status: z.enum(["incomplete", "ready", "claimed"]),
|
|
});
|
|
export type GoalState = z.infer<typeof goalStateSchema>;
|
|
|
|
export const playerStateSchema = z.object({
|
|
company: companySchema,
|
|
ships: z.array(shipSchema),
|
|
pilots: z.array(pilotSchema),
|
|
bays: z.array(baySchema),
|
|
locations: z.array(locationStateSchema),
|
|
tutorial: tutorialStateSchema.nullable(),
|
|
/** In-flight dispatches, including the tutorial run. */
|
|
activeDispatches: z.array(dispatchSchema),
|
|
/** Unacknowledged "while you were away" entries. */
|
|
summaries: z.array(resolutionSummarySchema),
|
|
/** Onboarding goals in content order; empty during the tutorial. */
|
|
goals: z.array(goalStateSchema),
|
|
});
|
|
export type PlayerState = z.infer<typeof playerStateSchema>;
|