deadhead-space-freight-tycoon/packages/sim/src/tutorial.ts

59 lines
2.2 KiB
TypeScript

import type { Location, ShipModel } from "@deadhead/content";
import { buildContract, standardCargo, type SimContent } from "./board";
import type { Contract } from "./contract";
import { createRng, deriveSeed, type Seed } from "./rng";
/** The tutorial loaner is the company's Courier. */
export function loanerModel(content: SimContent): ShipModel {
const courier = content.shipModels.find((s) => s.archetype === "courier");
if (!courier) throw new Error("Content has no Courier ship model");
return courier;
}
export type TutorialLocationId = "earth" | "moon";
export interface TutorialBoardInput {
/** Where the loaner currently is; contracts start here (decision 0.5.3). */
loanerLocationId: TutorialLocationId;
periodIndex: number;
worldSeed: Seed;
}
function location(id: string, content: SimContent): Location {
const found = content.locations.find((l) => l.id === id);
if (!found) throw new Error(`Unknown location ${id}`);
return found;
}
/**
* The tutorial "company board": Earth↔Moon only, Standard cargo sized to fit
* the loaner, starting at the loaner's location. Each payout is the tutorial
* share of the equivalent normal payout, rounded to whole credits.
*/
export function generateTutorialBoard(input: TutorialBoardInput, content: SimContent): Contract[] {
const { loanerLocationId, periodIndex, worldSeed } = input;
const { config } = content;
const origin = location(loanerLocationId, content);
const destination = location(loanerLocationId === "earth" ? "moon" : "earth", content);
const cargoType = standardCargo(content);
const maxMass = Math.min(config.contractMassTonnes.max, loanerModel(content).cargoCapacityTonnes);
const rng = createRng(deriveSeed(worldSeed, `tutorial:${loanerLocationId}:${periodIndex}`));
return Array.from({ length: config.tutorialBoardSize }, (_, i) => {
const contract = buildContract(
{
id: `tutorial:${loanerLocationId}:${periodIndex}:${i}`,
origin,
destination,
cargoType,
tier: 0,
massTonnes: rng.int(config.contractMassTonnes.min, maxMass),
firstContact: false,
periodIndex,
},
content,
);
return { ...contract, payout: Math.round(contract.payout * config.tutorialPayoutShare) };
});
}