import { content } from "@deadhead/content"; import { MS_PER_MINUTE } from "@deadhead/sim"; import { describe, expect, it, vi } from "vitest"; import { turnaroundMs } from "../src/game/independent"; import { ResolutionLoop, type LoopHost } from "../src/index"; import { addPilot, addShip, newTestApp, putBoard, readSave, setCredits, signUpOwner, testContract, TEST_NOW, type TestApp, } from "./support"; const FIRST_LOOK = content.config.independentPilots.firstLookRealMinutes * MS_PER_MINUTE; /** An owner with the Dispatch Office and two Couriers at Earth with hired (Manual) pilots. */ async function setup(): Promise { const test = newTestApp(); test.clock.now = TEST_NOW + FIRST_LOOK; const { app } = test; await signUpOwner(app); await setCredits(app, content.config.independentPilots.dispatchOfficePrice + 50_000); await app.call("buyDispatchOffice", {}); for (const id of ["pa", "pb"]) { await addShip(app, { id: `ship-${id}`, modelId: "courier", name: `Courier ${id}` }); await addPilot(app, { id, name: `Pilot ${id}`, assignedShipId: `ship-${id}` }); } await putBoard( app, [ testContract({ id: "earth:best", payout: 5_000 }), testContract({ id: "earth:next", payout: 3_000 }), ], test.clock.now, ); return test; } const takenBy = async (test: TestApp, pilotId: string) => (await readSave(test.app)).dispatches.find((d) => d.pilotId === pilotId && d.independent)?.record .contract?.id; describe("Independent pilots in live play (Step 20.5, decision 0.5.122)", () => { it("leaves the contract the player has open, and takes it once it's closed", async () => { const test = await setup(); const { app, clock } = test; app.setViewedContract("earth:best"); await app.call("setPilotMode", { pilotId: "pa", mode: "independent" }); clock.now += turnaroundMs(); await app.call("getPlayerState", {}); expect(await takenBy(test, "pa")).toBe("earth:next"); // The player closes the sheet without taking it: the next pilot may. app.setViewedContract(null); await app.call("setPilotMode", { pilotId: "pb", mode: "independent" }); clock.now += turnaroundMs(); await app.call("getPlayerState", {}); expect(await takenBy(test, "pb")).toBe("earth:best"); }); it("runs pilots' decisions from the resolution loop while the app is open", async () => { const test = await setup(); await test.app.call("setPilotMode", { pilotId: "pa", mode: "independent" }); let tick: () => void = () => {}; const host: LoopHost = { setInterval: (fn) => { tick = fn; return 1; }, clearInterval: () => {}, onResume: () => () => {}, }; const onChange = vi.fn(); const loop = new ResolutionLoop(test.app, host, onChange); await loop.start(); expect(await takenBy(test, "pa")).toBeUndefined(); test.clock.now += turnaroundMs(); tick(); await loop.tick(); expect(await takenBy(test, "pa")).toBe("earth:best"); expect(onChange).toHaveBeenCalled(); loop.stop(); }); });