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

98 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { SimContent } from "./board";
import type { RealTime } from "./clock";
import type { DispatchRecord } from "./dispatch";
import { dutyAfterTrip, dutyLimit, needsRest, restEndsAt, tripGameHours } from "./duty";
import { splitLoanRepayment } from "./loan";
import type { SkillLevels } from "./pilot";
import { deliveryXp } from "./xp";
export interface Resolution {
/**
* Credits to add: payout minus pilot pay, minus the loan repayment.
* Fuel was already charged at departure.
*/
credits: number;
/** Credits that went to the lender out of this payout (decision 0.5.32). */
loanRepayment: number;
pilotXp: number;
/** Where the ship (or tutorial loaner) is now. */
shipLocationId: string;
/** True when this delivery discovers the destination. */
discoversDestination: boolean;
/** The pilot's duty hours after this trip: 0 if it ended in mandatory rest (CR-004). */
pilotDutyHours: number;
/** Mandatory rest from the arrival time, or null (decision 0.5.45). */
pilotRestEndsAt: RealTime | null;
}
export type ResolveResult =
{ status: "in-flight" } | { status: "resolved"; resolution: Resolution };
export interface ResolveInput {
record: DispatchRecord;
/** The avatar is exempt from pilot pay (decision 0.5.12). */
pilotIsAvatar: boolean;
discoveredIds: ReadonlySet<string>;
/** The company's loan balance before this resolution. */
loanBalance: number;
/** The pilot's duty hours before this trip, and skills (Long Haul sets the limit). */
pilotDutyHours: number;
pilotSkills: SkillLevels;
now: RealTime;
}
/**
* The pilot's duty after a trip (decisions 0.5.42–0.5.45): the trip's game
* hours are added (tutorial runs add none); at or over the limit, rest starts
* at the arrival time and duty resets, however late the trip is resolved.
*/
function dutyOutcome(input: ResolveInput, content: SimContent) {
const { record, pilotDutyHours, pilotSkills } = input;
const hours = tripGameHours(record.departureAt, record.arrivalAt, content.config);
const duty = dutyAfterTrip(pilotDutyHours, hours, record.tutorial);
if (!record.tutorial && needsRest(duty, dutyLimit(pilotSkills, content.config))) {
return { pilotDutyHours: 0, pilotRestEndsAt: restEndsAt(record.arrivalAt, content.config) };
}
return { pilotDutyHours: duty, pilotRestEndsAt: null };
}
/** Resolves a dispatch once it has arrived. Same inputs always give the same result. */
export function resolveDispatch(input: ResolveInput, content: SimContent): ResolveResult {
const { record, pilotIsAvatar, discoveredIds, loanBalance, now } = input;
if (now < record.arrivalAt) return { status: "in-flight" };
const { contract } = record;
const base = { shipLocationId: record.destinationId, ...dutyOutcome(input, content) };
if (contract === null) {
// Deadheads earn nothing and only fly to discovered locations.
return {
status: "resolved",
resolution: {
...base,
credits: 0,
loanRepayment: 0,
pilotXp: 0,
discoversDestination: false,
},
};
}
const pilotPay = record.tutorial || pilotIsAvatar ? 0 : content.config.pilotPayPerDelivery;
// Tutorial contracts already store exactly the tutorial share as their payout.
const { repayment, net } = splitLoanRepayment(
contract.payout - pilotPay,
loanBalance,
content.config.loanRepaymentShare,
);
return {
status: "resolved",
resolution: {
...base,
credits: net,
loanRepayment: repayment,
pilotXp: deliveryXp(record.distanceMillionKm, contract.tier, content.config),
discoversDestination: !discoveredIds.has(record.destinationId),
},
};
}