1
0
Fork 0
polaris-task-force/src/scripts/gameTick.ts
Z8MB1E 5be390012a feat(base): timestamp-based construction lifecycle with progress panel
Blueprints declare constructionDurationMinutes instead of tick counts. Delivering the last material starts construction at delivery time via maybeStartConstruction, setting constructionStartedAt/CompletesAt on the site; the game tick only sweeps completed sites. Structure detail page extracts server data helpers and shows a ConstructionPanel with a literal ETA countdown.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-09-16 23:14:07 -04:00

81 lines
2.7 KiB
TypeScript

import type { SanitizedConfig } from "payload";
import { getPayload } from "payload";
import type { GameStructure, GameVehicle, Shipment, Vehicle } from "@/payload-types";
import { processShipmentTick } from "./processShipmentTick";
import { processConstructionTick } from "@/lib/map/construction";
import { createBinLogger, errorMessage } from "./lib/binFileLogger";
import { emitGameEvent } from "@/utils/event-log/emit";
import { EventTypes } from "@/utils/event-log/eventTypes";
export const script = async (config: SanitizedConfig) => {
const payload = await getPayload({ config });
const log = createBinLogger("game-tick", payload.logger);
try {
log.info("[Game Tick] Starting game tick...");
const activeShipments = await payload.find({
collection: "shipments",
where: {
status: { in: ["dispatched", "in_transit"] },
},
limit: 100,
depth: 2,
});
let processedCount = 0;
for (const shipment of activeShipments.docs) {
try {
await processShipmentTick(
payload,
shipment as unknown as Shipment & {
transportVehicle: GameVehicle & { type: Vehicle };
origin: GameStructure;
destination: GameStructure;
},
log,
);
processedCount++;
} catch (error) {
log.error(`[Game Tick] Error processing shipment ${shipment.id}: ${errorMessage(error)}`);
}
}
const construction = await processConstructionTick(payload, log);
await emitGameEvent(payload, {
type: EventTypes.GameTick,
message: `Game tick processed ${processedCount} of ${activeShipments.docs.length} active shipments`,
data: {
processedCount,
totalActive: activeShipments.docs.length,
construction,
},
});
const notifyUrl = (process.env.APP_URL ?? "http://localhost:3000").replace(/\/$/, "");
const notifySecret = process.env.GAME_TICK_NOTIFY_SECRET;
if (notifySecret) {
await fetch(`${notifyUrl}/api/game-tick/notify`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-game-tick-secret": notifySecret,
},
body: JSON.stringify({ processedAt: new Date().toISOString() }),
}).catch((error: unknown) => {
log.error(`[Game Tick] Failed to notify app clients: ${errorMessage(error)}`);
});
} else {
log.warn("[Game Tick] GAME_TICK_NOTIFY_SECRET not set; skipping client notification");
}
log.info(`[Game Tick] Complete. Processed ${processedCount} shipments.`);
process.exit(0);
} catch (error) {
log.error(
`[Game Tick] Fatal: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`,
);
process.exit(1);
}
};