diff --git a/app/game-api/package.json b/app/game-api/package.json index 59f96fe..46eb99a 100644 --- a/app/game-api/package.json +++ b/app/game-api/package.json @@ -6,6 +6,8 @@ "scripts": { "build": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/game-api", "dev": "tsdown -c ../../tsdown.config.ts -F @sammo-ts/game-api --watch", + "worker": "GAME_API_ROLE=battle-sim-worker node dist/index.js", + "worker:dev": "GAME_API_ROLE=battle-sim-worker tsdown -c ../../tsdown.config.ts -F @sammo-ts/game-api --watch", "lint": "node -e \"console.log('lint not configured')\"", "test": "vitest run --config vitest.config.ts", "typecheck": "tsc --noEmit" diff --git a/app/game-api/src/battleSim/schema.ts b/app/game-api/src/battleSim/schema.ts new file mode 100644 index 0000000..a16cdbb --- /dev/null +++ b/app/game-api/src/battleSim/schema.ts @@ -0,0 +1,98 @@ +import { z } from 'zod'; + +import type { BattleSimRequestPayload } from './types.js'; + +export const zBattleSimGeneral = z.object({ + no: z.number().int().positive(), + name: z.string().min(1), + nation: z.number().int().positive(), + turntime: z.string().min(1), + personal: z.string().nullable(), + special2: z.string().nullable(), + crew: z.number().int().min(0), + crewtype: z.number().int().positive(), + atmos: z.number().int().min(0), + train: z.number().int().min(0), + intel: z.number().int().min(0), + intel_exp: z.number().int().min(0), + book: z.string().nullable(), + strength: z.number().int().min(0), + strength_exp: z.number().int().min(0), + weapon: z.string().nullable(), + injury: z.number().int().min(0), + leadership: z.number().int().min(0), + leadership_exp: z.number().int().min(0), + horse: z.string().nullable(), + item: z.string().nullable(), + explevel: z.number().int().min(0), + experience: z.number().int().min(0), + dedication: z.number().int().min(0), + officer_level: z.number().int().min(1), + officer_city: z.number().int().positive(), + gold: z.number().int().min(0), + rice: z.number().int().min(0), + dex1: z.number().int().min(0), + dex2: z.number().int().min(0), + dex3: z.number().int().min(0), + dex4: z.number().int().min(0), + dex5: z.number().int().min(0), + recent_war: z.string().nullable(), + warnum: z.number().int().min(0), + killnum: z.number().int().min(0), + killcrew: z.number().int().min(0), + inheritBuff: z.union([z.record(z.string(), z.number()), z.array(z.number())]).optional(), +}); + +export const zBattleSimCity = z.object({ + city: z.number().int().positive(), + nation: z.number().int().min(0), + supply: z.number().int().min(0), + name: z.string().min(1), + pop: z.number().min(0), + agri: z.number().min(0), + comm: z.number().min(0), + secu: z.number().min(0), + def: z.number().min(0), + wall: z.number().min(0), + trust: z.number().min(0), + level: z.number().int().min(1), + pop_max: z.number().min(0), + agri_max: z.number().min(0), + comm_max: z.number().min(0), + secu_max: z.number().min(0), + def_max: z.number().min(0), + wall_max: z.number().min(0), + dead: z.number().min(0), + state: z.number().int().min(0), + conflict: z.string(), +}); + +export const zBattleSimNation = z.object({ + type: z.string().min(1), + tech: z.number().min(0), + level: z.number().int().min(1), + capital: z.number().int().min(0), + nation: z.number().int().min(0), + name: z.string().min(1), + gold: z.number().min(0), + rice: z.number().min(0), + gennum: z.number().int().min(1), +}); + +export const zBattleSimRequest: z.ZodType = z.object({ + action: z.enum(['reorder', 'battle']), + seed: z.string().optional(), + repeatCnt: z.number().int().min(1).max(1000), + year: z.number().int().min(0), + month: z.number().int().min(1).max(12), + attackerGeneral: zBattleSimGeneral, + attackerCity: zBattleSimCity, + attackerNation: zBattleSimNation, + defenderGenerals: z.array(zBattleSimGeneral), + defenderCity: zBattleSimCity, + defenderNation: zBattleSimNation, +}); + +export const zBattleSimJobId = z.object({ + jobId: z.string().min(1), +}); diff --git a/app/game-api/src/router.ts b/app/game-api/src/router.ts index 707f74a..274a1e9 100644 --- a/app/game-api/src/router.ts +++ b/app/game-api/src/router.ts @@ -28,7 +28,7 @@ import { type MessageView, } from './messages/store.js'; import { buildBattleSimJobPayload } from './battleSim/environment.js'; -import type { BattleSimRequestPayload } from './battleSim/types.js'; +import { zBattleSimJobId, zBattleSimRequest } from './battleSim/schema.js'; const zRunReason = z.enum(['schedule', 'manual', 'poke']); const zMessageType = z.enum(['private', 'public', 'national', 'diplomacy']); @@ -39,96 +39,6 @@ const zTurnRunBudget = z.object({ catchUpCap: z.number().int().positive(), }); -const zBattleSimGeneral = z.object({ - no: z.number().int().positive(), - name: z.string().min(1), - nation: z.number().int().positive(), - turntime: z.string().min(1), - personal: z.string().nullable(), - special2: z.string().nullable(), - crew: z.number().int().min(0), - crewtype: z.number().int().positive(), - atmos: z.number().int().min(0), - train: z.number().int().min(0), - intel: z.number().int().min(0), - intel_exp: z.number().int().min(0), - book: z.string().nullable(), - strength: z.number().int().min(0), - strength_exp: z.number().int().min(0), - weapon: z.string().nullable(), - injury: z.number().int().min(0), - leadership: z.number().int().min(0), - leadership_exp: z.number().int().min(0), - horse: z.string().nullable(), - item: z.string().nullable(), - explevel: z.number().int().min(0), - experience: z.number().int().min(0), - dedication: z.number().int().min(0), - officer_level: z.number().int().min(1), - officer_city: z.number().int().positive(), - gold: z.number().int().min(0), - rice: z.number().int().min(0), - dex1: z.number().int().min(0), - dex2: z.number().int().min(0), - dex3: z.number().int().min(0), - dex4: z.number().int().min(0), - dex5: z.number().int().min(0), - recent_war: z.string().nullable(), - warnum: z.number().int().min(0), - killnum: z.number().int().min(0), - killcrew: z.number().int().min(0), - inheritBuff: z.union([z.record(z.string(), z.number()), z.array(z.number())]).optional(), -}); - -const zBattleSimCity = z.object({ - city: z.number().int().positive(), - nation: z.number().int().min(0), - supply: z.number().int().min(0), - name: z.string().min(1), - pop: z.number().min(0), - agri: z.number().min(0), - comm: z.number().min(0), - secu: z.number().min(0), - def: z.number().min(0), - wall: z.number().min(0), - trust: z.number().min(0), - level: z.number().int().min(1), - pop_max: z.number().min(0), - agri_max: z.number().min(0), - comm_max: z.number().min(0), - secu_max: z.number().min(0), - def_max: z.number().min(0), - wall_max: z.number().min(0), - dead: z.number().min(0), - state: z.number().int().min(0), - conflict: z.string(), -}); - -const zBattleSimNation = z.object({ - type: z.string().min(1), - tech: z.number().min(0), - level: z.number().int().min(1), - capital: z.number().int().min(0), - nation: z.number().int().min(0), - name: z.string().min(1), - gold: z.number().min(0), - rice: z.number().min(0), - gennum: z.number().int().min(1), -}); - -const zBattleSimRequest: z.ZodType = z.object({ - action: z.enum(['reorder', 'battle']), - seed: z.string().optional(), - repeatCnt: z.number().int().min(1).max(1000), - year: z.number().int().min(0), - month: z.number().int().min(1).max(12), - attackerGeneral: zBattleSimGeneral, - attackerCity: zBattleSimCity, - attackerNation: zBattleSimNation, - defenderGenerals: z.array(zBattleSimGeneral), - defenderCity: zBattleSimCity, - defenderNation: zBattleSimNation, -}); const buildShiftAmountSchema = (maxTurns: number) => z.number() @@ -177,11 +87,7 @@ export const appRouter = router({ return ctx.battleSim.simulate(payload); }), getSimulation: procedure - .input( - z.object({ - jobId: z.string().min(1), - }) - ) + .input(zBattleSimJobId) .query(async ({ ctx, input }) => { const result = await ctx.battleSim.getSimulationResult(input.jobId); if (!result) { diff --git a/app/game-api/src/server.ts b/app/game-api/src/server.ts index d476e64..552ac63 100644 --- a/app/game-api/src/server.ts +++ b/app/game-api/src/server.ts @@ -101,6 +101,7 @@ export const createGameApiServer = async () => { profile: config.profileName, })); + app.addHook('onClose', async () => { await flushSubscriber.stop(); await flushSubscriberClient.quit();