feat(api): jump + explore endpoints queue tasks with lead + helpers instead of resolving instantly (#44)
This commit is contained in:
+57
-2
@@ -293,6 +293,27 @@ export async function jumpToSystem(db: GameDb, systemId: number): Promise<WorldS
|
||||
return getWorldState(db)
|
||||
}
|
||||
|
||||
// The jump pre-checks, extracted for the queueing route (5a): the same codes and messages
|
||||
// as jumpToSystem — unknown system (404), already there (409), outside range (409) — and
|
||||
// the hop count (reachableWithin + NAV AI, the existing reachability logic) the route puts
|
||||
// in the queued JUMP payload.
|
||||
export async function checkJumpTarget(db: GameDb, systemId: number): Promise<number> {
|
||||
await ensureSave(db)
|
||||
const systems = await db.db.select().from(schema.solarSystems)
|
||||
|
||||
const target = systems.find((s) => s.id === systemId)
|
||||
if (!target) throw new JumpError(404, `Unknown system ${systemId}`)
|
||||
const current = systems.find((s) => s.isCurrent)!
|
||||
if (current.id === target.id) throw new JumpError(409, 'Already at this system')
|
||||
|
||||
const routes = await db.db.select().from(schema.jumpRoutes)
|
||||
// #35 — NAVIGATION AI extends the jump range from adjacent (1 hop) to 2 hops.
|
||||
const navAi = (await db.db.select().from(schema.tech)).some((t) => t.slug === 'NAV_AI' && t.researched)
|
||||
const hopCount = reachableWithin(routes.map((r) => [r.fromSystem, r.toSystem]), current.id, navAi ? 2 : 1).get(target.id)
|
||||
if (hopCount === undefined) throw new JumpError(409, `${target.name} is not reachable from ${current.name}`)
|
||||
return hopCount
|
||||
}
|
||||
|
||||
// --- Local system + exploration ---------------------------------------------
|
||||
|
||||
export interface PlanetDto {
|
||||
@@ -438,6 +459,16 @@ export async function explorePlanet(db: GameDb, planetId: number): Promise<Explo
|
||||
return { ...(await readSystemState(db)), outcome, crewFound }
|
||||
}
|
||||
|
||||
// The explore pre-checks, extracted for the queueing route (5a): the same codes and messages
|
||||
// as explorePlanet — unknown body (404), already scanned (409).
|
||||
export async function checkExploreTarget(db: GameDb, planetId: number): Promise<void> {
|
||||
await ensureSave(db)
|
||||
const planets = await db.db.select().from(schema.planets)
|
||||
const target = planets.find((p) => p.id === planetId)
|
||||
if (!target) throw new ExploreError(404, `Unknown planet ${planetId}`)
|
||||
if (target.explored) throw new ExploreError(409, `${target.name} was already explored`)
|
||||
}
|
||||
|
||||
// --- The turn tick on the DB (POST /api/turn — Task 5 wires the route) ---------------------
|
||||
//
|
||||
// The route→save pattern: ensureSave first, unscoped single-player reads, persist last.
|
||||
@@ -462,7 +493,10 @@ function rowToTask(row: { id: number; saveId: number; type: string; payload: str
|
||||
return { id: row.id, saveId: row.saveId, type: row.type as TaskType, payload: row.payload, leadCrewId: row.leadCrewId, helpers: JSON.parse(row.helpers) as number[], duration: row.duration, remaining: row.remaining, status: row.status as TaskStatus, queuedAtTurn: row.queuedAtTurn }
|
||||
}
|
||||
|
||||
export async function runTurn(db: GameDb): Promise<TickResult> {
|
||||
// The plain queue snapshot Task 3's engine runs over, mapped from the schema's rows: crew,
|
||||
// tasks, inventory, shipSystems and tech, all in id order (single save, unscoped). The route
|
||||
// never touches lib/db/schema, so the single save row is resolved here, not passed in.
|
||||
export async function queueStateFromSave(db: GameDb): Promise<QueueState> {
|
||||
await ensureSave(db)
|
||||
const [save] = await db.db.select().from(schema.saves).limit(1)
|
||||
const [crew, taskRows, inventory, shipSystems, tech] = await Promise.all([
|
||||
@@ -472,7 +506,7 @@ export async function runTurn(db: GameDb): Promise<TickResult> {
|
||||
db.db.select().from(schema.shipSystems),
|
||||
db.db.select().from(schema.tech),
|
||||
])
|
||||
const state: QueueState = {
|
||||
return {
|
||||
turn: save.turn,
|
||||
crew: crew.map((c) => ({ id: c.id, name: c.name, role: c.role, traitGood: c.traitGood, traitBad: c.traitBad, origin: c.origin })),
|
||||
tasks: taskRows.map(rowToTask),
|
||||
@@ -480,6 +514,27 @@ export async function runTurn(db: GameDb): Promise<TickResult> {
|
||||
shipSystems: shipSystems.map((s) => ({ id: s.id, type: s.type, level: s.level, capacity: s.capacity })),
|
||||
tech: tech.map((t) => ({ id: t.id, slug: t.slug })),
|
||||
}
|
||||
}
|
||||
|
||||
// The route's DB-boundary insert (the route→save pattern): queueTask's row minus the two
|
||||
// values only the DB boundary knows (id autoincrement, saveId = the single save's id).
|
||||
// payload is already a JSON string from queueTask (passed through); the crew ids serialize
|
||||
// to the JSON column. Persists so the queue survives a crash; returns the new row id.
|
||||
export async function saveTaskRow(db: GameDb, task: Omit<Task, 'id' | 'saveId'>): Promise<number> {
|
||||
await ensureSave(db)
|
||||
const [save] = await db.db.select().from(schema.saves).limit(1)
|
||||
const [row] = await db.db
|
||||
.insert(schema.tasks)
|
||||
.values({ saveId: save.id, type: task.type, payload: task.payload, leadCrewId: task.leadCrewId, helpers: JSON.stringify(task.helpers), duration: task.duration, remaining: task.remaining, status: task.status, queuedAtTurn: task.queuedAtTurn })
|
||||
.returning({ id: schema.tasks.id })
|
||||
await persist(db)
|
||||
return row.id
|
||||
}
|
||||
|
||||
export async function runTurn(db: GameDb): Promise<TickResult> {
|
||||
await ensureSave(db)
|
||||
const [save] = await db.db.select().from(schema.saves).limit(1)
|
||||
const state = await queueStateFromSave(db)
|
||||
|
||||
const result = tickTurn(state)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user