import { content } from "@deadhead/content"; import type { PlayerState, Ship } from "@deadhead/schema"; import { checkRoute, effectiveStats } from "@deadhead/sim"; export interface OriginReach { originId: string; /** Purchasable models in range from there, then the player's upgraded ships there. */ reachers: string[]; } /** * Where the player could fly from: discovered locations with one of their HQs * or idle ships (CR-012). Earth when there are none yet (the tutorial). */ export function playerOrigins(state: Pick): string[] { const used = new Set([ ...state.bays.map((b) => b.locationId), ...state.ships.flatMap((s) => (s.locationId ? [s.locationId] : [])), ]); const origins = state.locations.filter((l) => l.discovered && used.has(l.id)).map((l) => l.id); return origins.length > 0 ? origins : ["earth"]; } /** * Who can fly to `locationId` right now, per origin (Steps 10A.16, 10L.8): * purchasable models whose range from that origin covers the leg (the sim's * route check, with the origin's range factor), and the player's ships at that * origin that get there only thanks to their upgrades (CR-009). Origins with * nobody are left out. */ export function reachersByOrigin( state: Pick, locationId: string, ): OriginReach[] { const target = content.location(locationId); if (!target) return []; return playerOrigins(state) .filter((id) => id !== locationId) .map((originId) => { const origin = content.location(originId)!; const reaches = (stats: Parameters[0]) => checkRoute(stats, origin, target, content.config).ok; const models = content.purchasableShipModels.filter(reaches).map((m) => m.name); const upgraded = state.ships .filter((ship: Ship) => { const model = content.shipModel(ship.modelId); if (ship.locationId !== originId || !model?.purchasable) return false; if (Object.keys(ship.upgrades).length === 0 || reaches(model)) return false; return reaches(effectiveStats(model, ship.upgrades, content)); }) .map((ship) => `your ${ship.name} (upgraded)`); return { originId, reachers: [...models, ...upgraded] }; }) .filter((o) => o.reachers.length > 0); }