deadhead-space-freight-tycoon/packages/engine/src/game/independent.ts

108 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 { content } from "@deadhead/content";
import type {
Company,
DispatchOffice,
Pilot,
PilotMode,
PilotOrders,
SaveDocument,
} from "@deadhead/schema";
import { defaultOrders, type RealTime } from "@deadhead/sim";
import { fail } from "./errors";
import {
companyView,
hasBayAt,
requirePilot,
requireTutorialOver,
spend,
toApiPilot,
} from "./state";
/** A new pilot's orders (CR-020 §2): anything goes, home at `homeLocationId`. */
export function newPilotOrders(homeLocationId: string): PilotOrders {
const orders = defaultOrders(homeLocationId, content);
return { ...orders, cargoClassIds: [...orders.cargoClassIds], destinationIds: null };
}
export function dispatchOfficeView(save: SaveDocument): DispatchOffice {
return { owned: save.company.dispatchOffice, spendingFloor: save.company.npcSpendingFloor };
}
/** The one-time Dispatch Office (decision 0.5.119). */
export function buyDispatchOffice(save: SaveDocument): {
company: Company;
dispatchOffice: DispatchOffice;
} {
requireTutorialOver(save, "buying a Dispatch Office");
if (save.company.dispatchOffice) {
throw fail("dispatch-office-owned", "You already have a Dispatch Office.");
}
spend(save, content.config.independentPilots.dispatchOfficePrice, "a Dispatch Office");
save.company.dispatchOffice = true;
return { company: companyView(save), dispatchOffice: dispatchOfficeView(save) };
}
/**
* Manual or Independent (decisions 0.5.118–0.5.120). Independent needs the
* Dispatch Office and is never the avatar. Switching to Manual mid-trip takes
* effect when the trip ends: an Independent pilot only acts when idle.
*/
export function setPilotMode(
save: SaveDocument,
input: { pilotId: string; mode: PilotMode },
now: RealTime,
): Pilot {
const pilot = requirePilot(save, input.pilotId);
if (input.mode === "independent") {
if (pilot.isAvatar) {
throw fail(
"avatar-not-independent",
"You fly your own jobs: only hired pilots can go Independent.",
);
}
if (!save.company.dispatchOffice) {
throw fail("dispatch-office-required", "Buy a Dispatch Office first.");
}
}
pilot.mode = input.mode;
return toApiPilot(save, pilot, now);
}
/** A pilot's orders (decision 0.5.120), checked against content and the player's bays. */
export function setPilotOrders(
save: SaveDocument,
input: { pilotId: string; orders: PilotOrders },
now: RealTime,
): Pilot {
const pilot = requirePilot(save, input.pilotId);
const { orders } = input;
for (const id of orders.destinationIds ?? []) {
if (!content.location(id)) throw fail("unknown-location", `No location "${id}".`);
}
for (const id of orders.cargoClassIds) {
if (!content.cargoTypes.some((c) => c.id === id)) {
throw fail("unknown-cargo-class", `No cargo class "${id}".`);
}
}
if (!hasBayAt(save, orders.homeLocationId)) {
throw fail("no-bay-at-location", "A pilot's home must be one of your bays.");
}
pilot.orders = {
destinationIds: orders.destinationIds ? [...new Set(orders.destinationIds)] : null,
cargoClassIds: [...new Set(orders.cargoClassIds)],
takePriority: orders.takePriority,
minProfit: orders.minProfit,
homeLocationId: orders.homeLocationId,
};
return toApiPilot(save, pilot, now);
}
/** The credits Independent pilots never spend the company below (decision 0.5.123). */
export function setSpendingFloor(
save: SaveDocument,
credits: number,
): { company: Company; dispatchOffice: DispatchOffice } {
save.company.npcSpendingFloor = credits;
return { company: companyView(save), dispatchOffice: dispatchOfficeView(save) };
}