162 lines
6.3 KiB
TypeScript
162 lines
6.3 KiB
TypeScript
import { content } from "@deadhead/content";
|
|
import { MS_PER_MINUTE, upgradeShopMs } from "@deadhead/sim";
|
|
import { describe, expect, it } from "vitest";
|
|
import { turnaroundMs } from "../src/game/independent";
|
|
import {
|
|
addPilot,
|
|
addShip,
|
|
edit,
|
|
newTestApp,
|
|
readSave,
|
|
setCredits,
|
|
signUpOwner,
|
|
type TestApp,
|
|
} from "./support";
|
|
|
|
const SHOP_MS = upgradeShopMs(1, content.config);
|
|
const LIMIT = content.config.dutyLimitGameHours;
|
|
const pilotIn = async (test: TestApp, id: string) =>
|
|
(await test.app.call("getPlayerState", {})).pilots.find((p) => p.id === id)!;
|
|
|
|
/** An owner with a Courier at Earth crewed by `p1`, one trip short of the duty limit. */
|
|
async function tiredPilot(test: TestApp) {
|
|
await signUpOwner(test.app);
|
|
await setCredits(test.app, 100_000);
|
|
await addShip(test.app, { id: "f1", modelId: "courier", name: "Wren" });
|
|
await addPilot(test.app, { id: "p1", name: "Ada", assignedShipId: "f1", dutyHours: LIMIT - 0.5 });
|
|
}
|
|
|
|
const upgrade = (test: TestApp) =>
|
|
test.app.call("startUpgrade", { shipId: "f1", upgradeId: "engines" });
|
|
|
|
describe("shop time counts as rest (Step 23.2, decision 0.5.135)", () => {
|
|
it("brings a pilot near the duty limit out of the shop with no duty, ready to fly", async () => {
|
|
const test = newTestApp();
|
|
await tiredPilot(test);
|
|
const startedAt = test.clock.now;
|
|
await upgrade(test);
|
|
expect((await pilotIn(test, "p1")).restingInShopUntil).toBe(startedAt + SHOP_MS);
|
|
|
|
test.clock.now = startedAt + SHOP_MS;
|
|
const pilot = await pilotIn(test, "p1");
|
|
expect(pilot.dutyHours).toBe(0);
|
|
expect(pilot.resting).toBe(false);
|
|
expect(pilot.restingInShopUntil).toBeNull();
|
|
const { dispatch } = await test.app.call("createDispatch", {
|
|
kind: "reposition",
|
|
shipId: "f1",
|
|
pilotId: "p1",
|
|
destinationId: "moon",
|
|
});
|
|
expect(dispatch.status).toBe("in-flight");
|
|
});
|
|
|
|
it("keeps the duty of a pilot who left the ship and flew another one", async () => {
|
|
const test = newTestApp();
|
|
await tiredPilot(test);
|
|
await addShip(test.app, { id: "f2", modelId: "courier", name: "Kite" });
|
|
// Fresh enough that the Moon trip adds duty without forcing a rest.
|
|
await edit(test.app, (save) => {
|
|
save.pilots.find((p) => p.id === "p1")!.dutyHours = 2;
|
|
});
|
|
const startedAt = test.clock.now;
|
|
await upgrade(test);
|
|
await test.app.call("unassignPilot", { pilotId: "p1" });
|
|
expect((await pilotIn(test, "p1")).restingInShopUntil).toBeNull();
|
|
await test.app.call("assignPilot", { pilotId: "p1", shipId: "f2" });
|
|
await test.app.call("createDispatch", {
|
|
kind: "reposition",
|
|
shipId: "f2",
|
|
pilotId: "p1",
|
|
destinationId: "moon",
|
|
});
|
|
|
|
test.clock.now = startedAt + SHOP_MS + 60 * MS_PER_MINUTE;
|
|
const pilot = await pilotIn(test, "p1");
|
|
// At the Moon with the trip added to their duty, not reset by the shop.
|
|
expect(pilot.status).toBe("idle");
|
|
expect(pilot.dutyHours).toBeGreaterThan(2);
|
|
expect((await readSave(test.app)).ships.find((s) => s.id === "f1")!.shopPilotId).toBeNull();
|
|
});
|
|
|
|
it("doesn't reset a pilot who left the ship and came back during the upgrade", async () => {
|
|
const test = newTestApp();
|
|
await tiredPilot(test);
|
|
const startedAt = test.clock.now;
|
|
await upgrade(test);
|
|
await test.app.call("unassignPilot", { pilotId: "p1" });
|
|
await test.app.call("assignPilot", { pilotId: "p1", shipId: "f1" });
|
|
test.clock.now = startedAt + SHOP_MS;
|
|
expect((await pilotIn(test, "p1")).dutyHours).toBe(LIMIT - 0.5);
|
|
});
|
|
|
|
it("frees a pilot whose rest would have ended after the upgrade, and leaves a shorter rest alone", async () => {
|
|
const test = newTestApp();
|
|
await tiredPilot(test);
|
|
const startedAt = test.clock.now;
|
|
await addShip(test.app, { id: "f2", modelId: "courier", name: "Kite" });
|
|
await addPilot(test.app, { id: "p2", name: "Bo", assignedShipId: "f2" });
|
|
await edit(test.app, (save) => {
|
|
save.pilots.find((p) => p.id === "p1")!.restEndsAt = startedAt + 3 * SHOP_MS;
|
|
save.pilots.find((p) => p.id === "p2")!.restEndsAt = startedAt + SHOP_MS / 2;
|
|
});
|
|
await upgrade(test);
|
|
await test.app.call("startUpgrade", { shipId: "f2", upgradeId: "engines" });
|
|
|
|
// The short rest runs its course alongside the upgrade.
|
|
test.clock.now = startedAt + SHOP_MS / 2;
|
|
expect((await pilotIn(test, "p2")).resting).toBe(false);
|
|
expect((await pilotIn(test, "p1")).resting).toBe(true);
|
|
|
|
test.clock.now = startedAt + SHOP_MS;
|
|
const p1 = await pilotIn(test, "p1");
|
|
expect(p1.resting).toBe(false);
|
|
expect(p1.dutyHours).toBe(0);
|
|
});
|
|
|
|
it("brings an Independent pilot out rested, flying after the turnaround", async () => {
|
|
const test = newTestApp();
|
|
await tiredPilot(test);
|
|
await test.app.call("buyDispatchOffice", {});
|
|
await edit(test.app, (save) => {
|
|
save.pilots.find((p) => p.id === "p1")!.restEndsAt = test.clock.now + 3 * SHOP_MS;
|
|
});
|
|
const startedAt = test.clock.now;
|
|
await upgrade(test);
|
|
await test.app.call("setPilotMode", { pilotId: "p1", mode: "independent" });
|
|
|
|
test.clock.now = startedAt + SHOP_MS;
|
|
await test.app.call("getPlayerState", {});
|
|
const save = await readSave(test.app);
|
|
const p1 = save.pilots.find((p) => p.id === "p1")!;
|
|
expect(p1.dutyHours).toBe(0);
|
|
expect(p1.restEndsAt).toBe(startedAt + SHOP_MS);
|
|
expect(p1.nextDecisionAt).toBe(startedAt + SHOP_MS + turnaroundMs());
|
|
});
|
|
|
|
it("gives the same save caught up at once as in live play", async () => {
|
|
const once = newTestApp();
|
|
await tiredPilot(once);
|
|
await once.app.call("buyDispatchOffice", {});
|
|
await upgrade(once);
|
|
await once.app.call("setPilotMode", { pilotId: "p1", mode: "independent" });
|
|
const { code } = await once.app.call("exportSave", {});
|
|
const live = newTestApp();
|
|
live.clock.now = once.clock.now;
|
|
await live.app.call("importSave", { code });
|
|
|
|
const end = once.clock.now + 3 * 60 * MS_PER_MINUTE;
|
|
once.clock.now = end;
|
|
await once.app.call("getPlayerState", {});
|
|
while (live.clock.now < end) {
|
|
live.clock.now = Math.min(end, live.clock.now + 30_000);
|
|
await live.app.call("getPlayerState", {});
|
|
}
|
|
|
|
const a = await readSave(once.app);
|
|
const b = await readSave(live.app);
|
|
expect(a.dispatches.some((d) => d.independent)).toBe(true);
|
|
expect({ ...b, lastTimeSeen: 0 }).toEqual({ ...a, lastTimeSeen: 0 });
|
|
});
|
|
});
|