diff --git a/packages/logic/src/actions/turn/commandModule.ts b/packages/logic/src/actions/turn/commandModule.ts index 5b504f0..a4506bd 100644 --- a/packages/logic/src/actions/turn/commandModule.ts +++ b/packages/logic/src/actions/turn/commandModule.ts @@ -1,4 +1,5 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js'; +import type { ZodType } from 'zod'; import type { ActionContextBuilder } from './actionContext.js'; import type { TurnCommandEnv } from './commandEnv.js'; @@ -7,6 +8,7 @@ export interface TurnCommandSpecBase { category: string; reqArg: boolean; args: Record; + argsSchema?: ZodType; createDefinition(env: TurnCommandEnv): GeneralActionDefinition; } diff --git a/packages/logic/src/actions/turn/general/che_NPC능동.ts b/packages/logic/src/actions/turn/general/che_NPC능동.ts index b5d75b3..d8a2bdc 100644 --- a/packages/logic/src/actions/turn/general/che_NPC능동.ts +++ b/packages/logic/src/actions/turn/general/che_NPC능동.ts @@ -17,11 +17,6 @@ import type { GeneralTurnCommandSpec } from './index.js'; import type { MapDefinition } from '@sammo-ts/logic/world/types.js'; import { parseArgsWithSchema } from '../parseArgs.js'; -export interface NPCSelfArgs { - optionText: string; - destCityId?: number; -} - export type NPCSelfResolveContext = GeneralActionResolveContext & { map?: MapDefinition; @@ -29,12 +24,13 @@ export type NPCSelfResolveContext; export class ActionResolver< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -98,7 +94,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): NPCSelfArgs | null { - return parseArgsWithSchema(NPC_SELF_ARGS_SCHEMA, raw); + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(_ctx: ConstraintContext, args: NPCSelfArgs): Constraint[] { @@ -121,5 +117,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '특수', // Valid category? Legacy didn't specify category in static prop usually, handled by mapping. Defaulting to '특수'. reqArg: true, args: { optionText: '', destCityId: 0 }, + argsSchema: ARGS_SCHEMA, createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(), }; diff --git a/packages/logic/src/actions/turn/general/che_강행.ts b/packages/logic/src/actions/turn/general/che_강행.ts index fcbd7ec..f9ff159 100644 --- a/packages/logic/src/actions/turn/general/che_강행.ts +++ b/packages/logic/src/actions/turn/general/che_강행.ts @@ -24,10 +24,6 @@ import type { MapDefinition } from '@sammo-ts/logic/world/types.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import { parseArgsWithSchema } from '../parseArgs.js'; -export interface ForcedMoveArgs { - destCityId: number; -} - export interface ForcedMoveResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, > extends GeneralActionResolveContext { @@ -38,9 +34,10 @@ export interface ForcedMoveResolveContext< const ACTION_NAME = '강행'; const ACTION_KEY = 'che_강행'; -const FORCED_MOVE_ARGS_SCHEMA = z.object({ +const ARGS_SCHEMA = z.object({ destCityId: z.number(), }); +export type ForcedMoveArgs = z.infer; export class ActionResolver< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -164,7 +161,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): ForcedMoveArgs | null { - return parseArgsWithSchema(FORCED_MOVE_ARGS_SCHEMA, raw); + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(ctx: ConstraintContext, _args: ForcedMoveArgs): Constraint[] { @@ -199,5 +196,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '군사', reqArg: true, args: { destCityId: 0 }, + argsSchema: ARGS_SCHEMA, createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(), }; diff --git a/packages/logic/src/actions/turn/general/che_건국.ts b/packages/logic/src/actions/turn/general/che_건국.ts index 28536e8..832bdf3 100644 --- a/packages/logic/src/actions/turn/general/che_건국.ts +++ b/packages/logic/src/actions/turn/general/che_건국.ts @@ -17,17 +17,19 @@ import { createNationPatchEffect, } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; +import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; - -export interface FoundingArgs { - nationName: string; - nationType: string; - colorType: number; -} +import { parseArgsWithSchema } from '../parseArgs.js'; const ACTION_NAME = '건국'; +const ARGS_SCHEMA = z.object({ + nationName: z.string().min(1), + nationType: z.string().min(1), + colorType: z.number(), +}); +export type FoundingArgs = z.infer; const NATION_COLORS = [ '#FF0000', @@ -72,13 +74,7 @@ export class ActionDefinition< public readonly name = ACTION_NAME; parseArgs(raw: unknown): FoundingArgs | null { - if (typeof raw !== 'object' || raw === null) return null; - const { nationName, nationType, colorType } = raw as Record; - if (typeof nationName !== 'string' || !nationName) return null; - if (typeof nationType !== 'string' || !nationType) return null; - if (typeof colorType !== 'number') return null; - - return { nationName, nationType, colorType }; + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(_ctx: ConstraintContext, args: FoundingArgs): Constraint[] { @@ -174,5 +170,6 @@ export const commandSpec: GeneralTurnCommandSpec = { nationType: 'string', colorType: 'number', }, + argsSchema: ARGS_SCHEMA, createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(), }; diff --git a/packages/logic/src/actions/turn/general/che_군량매매.ts b/packages/logic/src/actions/turn/general/che_군량매매.ts index 4c27473..03a7484 100644 --- a/packages/logic/src/actions/turn/general/che_군량매매.ts +++ b/packages/logic/src/actions/turn/general/che_군량매매.ts @@ -4,14 +4,11 @@ import { occupiedCity, reqGeneralGold, reqGeneralRice } from '@sammo-ts/logic/co import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js'; import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; import { LogFormat } from '@sammo-ts/logic/logging/types.js'; +import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; - -export interface TradeArgs { - buyRice: boolean; - amount: number; -} +import { parseArgsWithSchema } from '../parseArgs.js'; export interface TradeEnvironment { exchangeFee?: number; @@ -19,6 +16,11 @@ export interface TradeEnvironment { const ACTION_NAME = '군량매매'; const DEFAULT_EXCHANGE_FEE = 0.01; +const ARGS_SCHEMA = z.object({ + buyRice: z.boolean(), + amount: z.number(), +}); +export type TradeArgs = z.infer; export class ActionDefinition< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -32,16 +34,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): TradeArgs | null { - if (typeof raw !== 'object' || raw === null) { - return null; - } - const record = raw as Record; - const buyRice = record.buyRice; - const amount = record.amount; - if (typeof buyRice !== 'boolean' || typeof amount !== 'number') { - return null; - } - return { buyRice, amount }; + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(_ctx: ConstraintContext, args: TradeArgs): Constraint[] { @@ -129,5 +122,6 @@ export const commandSpec: GeneralTurnCommandSpec = { buyRice: 'boolean', amount: 'number', }, + argsSchema: ARGS_SCHEMA, createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(), }; diff --git a/packages/logic/src/actions/turn/general/che_등용.ts b/packages/logic/src/actions/turn/general/che_등용.ts index 8dc35d4..4f612d4 100644 --- a/packages/logic/src/actions/turn/general/che_등용.ts +++ b/packages/logic/src/actions/turn/general/che_등용.ts @@ -16,13 +16,11 @@ import type { } from '@sammo-ts/logic/actions/engine.js'; import { createGeneralPatchEffect, createLogEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogScope } from '@sammo-ts/logic/logging/types.js'; +import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { ActionContextBase, ActionContextOptions } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; - -export interface EmployArgs { - destGeneralId: number; -} +import { parseArgsWithSchema } from '../parseArgs.js'; export interface EmployResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -33,6 +31,10 @@ export interface EmployResolveContext< const ACTION_NAME = '등용'; const ACTION_KEY = 'che_등용'; +const ARGS_SCHEMA = z.object({ + destGeneralId: z.number(), +}); +export type EmployArgs = z.infer; const differentNationDestGeneral = (): Constraint => ({ name: 'DifferentNationDestGeneral', @@ -145,9 +147,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): EmployArgs | null { - const data = raw as Partial; - if (typeof data.destGeneralId !== 'number') return null; - return { destGeneralId: data.destGeneralId }; + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(_ctx: ConstraintContext, args: EmployArgs): Constraint[] { @@ -205,5 +205,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '인사', reqArg: true, args: { destGeneralId: 0 }, + argsSchema: ARGS_SCHEMA, createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env), }; diff --git a/packages/logic/src/actions/turn/general/che_등용수락.ts b/packages/logic/src/actions/turn/general/che_등용수락.ts index c202a9e..416bef4 100644 --- a/packages/logic/src/actions/turn/general/che_등용수락.ts +++ b/packages/logic/src/actions/turn/general/che_등용수락.ts @@ -18,17 +18,19 @@ import type { import { createGeneralPatchEffect, createNationPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; +import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; - -export interface AcceptScoutArgs { - destNationId: number; - destGeneralId: number; -} +import { parseArgsWithSchema } from '../parseArgs.js'; const ACTION_NAME = '등용수락'; const ACTION_KEY = 'che_등용수락'; +const ARGS_SCHEMA = z.object({ + destNationId: z.number(), + destGeneralId: z.number(), +}); +export type AcceptScoutArgs = z.infer; export interface AcceptScoutResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -180,9 +182,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): AcceptScoutArgs | null { - const args = raw as Partial; - if (typeof args.destNationId !== 'number' || typeof args.destGeneralId !== 'number') return null; - return { destNationId: args.destNationId, destGeneralId: args.destGeneralId }; + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(_ctx: ConstraintContext, _args: AcceptScoutArgs): Constraint[] { @@ -224,5 +224,6 @@ export const commandSpec: GeneralTurnCommandSpec = { destNationId: 'number', destGeneralId: 'number', }, + argsSchema: ARGS_SCHEMA, createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env), }; diff --git a/packages/logic/src/actions/turn/general/che_선동.ts b/packages/logic/src/actions/turn/general/che_선동.ts index 3598b53..d3d8599 100644 --- a/packages/logic/src/actions/turn/general/che_선동.ts +++ b/packages/logic/src/actions/turn/general/che_선동.ts @@ -16,14 +16,12 @@ import type { } from '@sammo-ts/logic/actions/engine.js'; import { createGeneralPatchEffect, createCityPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; +import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { ActionContextBase, ActionContextOptions } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { JosaUtil } from '@sammo-ts/common'; - -export interface AgitateArgs { - destCityId: number; -} +import { parseArgsWithSchema } from '../parseArgs.js'; export interface AgitateResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -34,6 +32,10 @@ export interface AgitateResolveContext< const ACTION_NAME = '선동'; const ACTION_KEY = 'che_선동'; +const ARGS_SCHEMA = z.object({ + destCityId: z.number(), +}); +export type AgitateArgs = z.infer; export class ActionResolver< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -130,9 +132,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): AgitateArgs | null { - const data = raw as Partial; - if (typeof data.destCityId !== 'number') return null; - return { destCityId: data.destCityId }; + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(ctx: ConstraintContext, _args: AgitateArgs): Constraint[] { @@ -170,5 +170,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '군사', reqArg: true, args: { destCityId: 0 }, + argsSchema: ARGS_SCHEMA, createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env), }; diff --git a/packages/logic/src/actions/turn/general/che_숙련전환.ts b/packages/logic/src/actions/turn/general/che_숙련전환.ts index 2c6693e..0dd572d 100644 --- a/packages/logic/src/actions/turn/general/che_숙련전환.ts +++ b/packages/logic/src/actions/turn/general/che_숙련전환.ts @@ -9,11 +9,8 @@ import type { GeneralTurnCommandSpec } from './index.js'; import type { UnitSetDefinition } from '@sammo-ts/logic/world/types.js'; import { JosaUtil } from '@sammo-ts/common'; import { getMetaNumber, setMetaNumber, increaseMetaNumber } from '@sammo-ts/logic/war/utils.js'; - -export interface DexTransferArgs { - srcArmType: number; - destArmType: number; -} +import { z } from 'zod'; +import { parseArgsWithSchema } from '../parseArgs.js'; export interface DexTransferContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -28,16 +25,13 @@ export interface DexTransferEnvironment { const ACTION_NAME = '숙련전환'; const DECREASE_COEFF = 0.4; const CONVERT_COEFF = 0.9; - -const isRecord = (value: unknown): value is Record => - value !== null && typeof value === 'object' && !Array.isArray(value); - -const resolveArmType = (value: unknown): number | null => { - if (typeof value !== 'number' || !Number.isInteger(value)) { - return null; - } - return value > 0 ? value : null; -}; +const ARGS_SCHEMA = z + .object({ + srcArmType: z.number().int().positive(), + destArmType: z.number().int().positive(), + }) + .refine((data) => data.srcArmType !== data.destArmType); +export type DexTransferArgs = z.infer; const resolveArmTypeName = (unitSet: UnitSetDefinition | null | undefined, armType: number): string => unitSet?.armTypes?.[String(armType)] ?? `병종${armType}`; @@ -54,16 +48,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): DexTransferArgs | null { - const data = isRecord(raw) ? raw : {}; - const srcArmType = resolveArmType(data.srcArmType); - const destArmType = resolveArmType(data.destArmType); - if (srcArmType === null || destArmType === null) { - return null; - } - if (srcArmType === destArmType) { - return null; - } - return { srcArmType, destArmType }; + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(_ctx: ConstraintContext, _args: DexTransferArgs): Constraint[] { @@ -110,5 +95,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '군사', reqArg: true, args: { srcArmType: 0, destArmType: 0 }, + argsSchema: ARGS_SCHEMA, createDefinition: (env: TurnCommandEnv) => new ActionDefinition({ develCost: env.develCost }), }; diff --git a/packages/logic/src/actions/turn/general/che_이동.ts b/packages/logic/src/actions/turn/general/che_이동.ts index 7dee991..27843de 100644 --- a/packages/logic/src/actions/turn/general/che_이동.ts +++ b/packages/logic/src/actions/turn/general/che_이동.ts @@ -24,10 +24,6 @@ import type { GeneralTurnCommandSpec } from './index.js'; import type { MapDefinition } from '@sammo-ts/logic/world/types.js'; import { parseArgsWithSchema } from '../parseArgs.js'; -export interface MoveArgs { - destCityId: number; -} - export interface MoveResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, > extends GeneralActionResolveContext { @@ -38,9 +34,10 @@ export interface MoveResolveContext< const ACTION_NAME = '이동'; const ACTION_KEY = 'che_이동'; -const MOVE_ARGS_SCHEMA = z.object({ +const ARGS_SCHEMA = z.object({ destCityId: z.number(), }); +export type MoveArgs = z.infer; // Helper for map connectivity const connectedCity = (): Constraint => ({ @@ -160,7 +157,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): MoveArgs | null { - return parseArgsWithSchema(MOVE_ARGS_SCHEMA, raw); + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(ctx: ConstraintContext, _args: MoveArgs): Constraint[] { @@ -195,5 +192,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '군사', reqArg: true, args: { destCityId: 0 }, + argsSchema: ARGS_SCHEMA, createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(), }; diff --git a/packages/logic/src/actions/turn/general/che_임관.ts b/packages/logic/src/actions/turn/general/che_임관.ts index 8508819..47f9ec3 100644 --- a/packages/logic/src/actions/turn/general/che_임관.ts +++ b/packages/logic/src/actions/turn/general/che_임관.ts @@ -5,15 +5,17 @@ import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition import type { GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; import { createGeneralPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; +import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; - -export interface AppointmentArgs { - destNationId: number; -} +import { parseArgsWithSchema } from '../parseArgs.js'; const ACTION_NAME = '임관'; +const ARGS_SCHEMA = z.object({ + destNationId: z.number(), +}); +export type AppointmentArgs = z.infer; const parseNationId = (raw: unknown): number | null => { if (typeof raw !== 'number' || !Number.isFinite(raw)) { @@ -29,8 +31,11 @@ export class ActionDefinition< public readonly name = ACTION_NAME; parseArgs(raw: unknown): AppointmentArgs | null { - const data = raw as { destNationId?: unknown }; - const destNationId = parseNationId(data?.destNationId); + const data = parseArgsWithSchema(ARGS_SCHEMA, raw); + if (!data) { + return null; + } + const destNationId = parseNationId(data.destNationId); if (destNationId === null) { return null; } @@ -69,5 +74,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '전략', reqArg: true, args: { destNationId: 0 }, + argsSchema: ARGS_SCHEMA, createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(), }; diff --git a/packages/logic/src/actions/turn/general/che_징병.ts b/packages/logic/src/actions/turn/general/che_징병.ts index a0390c6..fe2da1c 100644 --- a/packages/logic/src/actions/turn/general/che_징병.ts +++ b/packages/logic/src/actions/turn/general/che_징병.ts @@ -19,6 +19,7 @@ import type { import type { MapDefinition, UnitSetDefinition } from '@sammo-ts/logic/world/types.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import { resolveStartYear } from '@sammo-ts/logic/actions/turn/actionContextHelpers.js'; +import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { @@ -27,11 +28,7 @@ import { getTechCost, isCrewTypeAvailable, } from '@sammo-ts/logic/world/unitSet.js'; - -export interface RecruitArgs { - crewType: number; - amount: number; -} +import { parseArgsWithSchema } from '../parseArgs.js'; export interface RecruitEnvironment { costOffset?: number; @@ -58,9 +55,27 @@ const DEFAULT_ATMOS = 40; const DEFAULT_MIN_POP = 30000; const DEFAULT_TRUST = 50; const MIN_CREW = 100; - -const isRecord = (value: unknown): value is Record => - value !== null && typeof value === 'object' && !Array.isArray(value); +const ARGS_SCHEMA = z.preprocess( + (raw) => { + if (!raw || typeof raw !== 'object' || Array.isArray(raw)) { + return raw; + } + const record = raw as Record; + const crewType = record.crewType ?? record.crewTypeId; + return { ...record, crewType }; + }, + z.object({ + crewType: z.preprocess( + (value) => (typeof value === 'number' ? Math.floor(value) : value), + z.number().int().positive() + ), + amount: z.preprocess( + (value) => (typeof value === 'number' ? Math.floor(value) : value), + z.number().int().min(0) + ), + }) +); +export type RecruitArgs = z.infer; const clamp = (value: number, min: number | null, max: number | null): number => { if (max !== null && min !== null && max < min) { @@ -401,13 +416,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): RecruitArgs | null { - const data = isRecord(raw) ? raw : {}; - const crewTypeId = resolveCrewTypeId(data); - const amount = resolveCrewAmount(data); - if (crewTypeId === null || amount === null) { - return null; - } - return { crewType: crewTypeId, amount }; + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(ctx: ConstraintContext, _args: RecruitArgs): Constraint[] { @@ -528,5 +537,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '내정', reqArg: true, args: {}, + argsSchema: ARGS_SCHEMA, createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? [], {}), }; diff --git a/packages/logic/src/actions/turn/general/che_첩보.ts b/packages/logic/src/actions/turn/general/che_첩보.ts index 8fa05c7..079ecdf 100644 --- a/packages/logic/src/actions/turn/general/che_첩보.ts +++ b/packages/logic/src/actions/turn/general/che_첩보.ts @@ -16,14 +16,12 @@ import type { } from '@sammo-ts/logic/actions/engine.js'; import { createGeneralPatchEffect, createNationPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; +import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { ActionContextBase, ActionContextOptions } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; import type { MapDefinition } from '@sammo-ts/logic/world/types.js'; - -export interface SpyArgs { - destCityId: number; -} +import { parseArgsWithSchema } from '../parseArgs.js'; export interface SpyResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -35,6 +33,10 @@ export interface SpyResolveContext< const ACTION_NAME = '첩보'; const ACTION_KEY = 'che_첩보'; +const ARGS_SCHEMA = z.object({ + destCityId: z.number(), +}); +export type SpyArgs = z.infer; export class ActionResolver< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -126,9 +128,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): SpyArgs | null { - const data = raw as Partial; - if (typeof data.destCityId !== 'number') return null; - return { destCityId: data.destCityId }; + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(ctx: ConstraintContext, _args: SpyArgs): Constraint[] { @@ -167,5 +167,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '군사', reqArg: true, args: { destCityId: 0 }, + argsSchema: ARGS_SCHEMA, createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(), }; diff --git a/packages/logic/src/actions/turn/general/che_출병.ts b/packages/logic/src/actions/turn/general/che_출병.ts index cce136a..9f48a8c 100644 --- a/packages/logic/src/actions/turn/general/che_출병.ts +++ b/packages/logic/src/actions/turn/general/che_출병.ts @@ -26,6 +26,7 @@ import { createNationPatchEffect, } from '@sammo-ts/logic/actions/engine.js'; import { JosaUtil, LiteHashDRBG } from '@sammo-ts/common'; +import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { GeneralTurnCommandSpec } from './index.js'; import type { WarAftermathConfig, WarEngineConfig, WarTimeContext } from '@sammo-ts/logic/war/types.js'; @@ -40,10 +41,7 @@ import { buildWarConfig, buildWarTime, } from '@sammo-ts/logic/actions/turn/actionContextHelpers.js'; - -export interface DispatchArgs { - destCityId: number; -} +import { parseArgsWithSchema } from '../parseArgs.js'; export interface DispatchResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -63,6 +61,10 @@ export interface DispatchResolveContext< } const ACTION_NAME = '출병'; +const ARGS_SCHEMA = z.object({ + destCityId: z.number(), +}); +export type DispatchArgs = z.infer; const parseCityId = (raw: unknown): number | null => { if (typeof raw !== 'number' || !Number.isFinite(raw)) { @@ -247,8 +249,11 @@ export class ActionDefinition< } parseArgs(raw: unknown): DispatchArgs | null { - const data = raw as { destCityId?: unknown }; - const destCityId = parseCityId(data?.destCityId); + const data = parseArgsWithSchema(ARGS_SCHEMA, raw); + if (!data) { + return null; + } + const destCityId = parseCityId(data.destCityId); if (destCityId === null) { return null; } @@ -534,5 +539,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '군사', reqArg: true, args: { destCityId: 0 }, + argsSchema: ARGS_SCHEMA, createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.warActionModules ?? []), }; diff --git a/packages/logic/src/actions/turn/general/che_탈취.ts b/packages/logic/src/actions/turn/general/che_탈취.ts index 5f2efb7..305513d 100644 --- a/packages/logic/src/actions/turn/general/che_탈취.ts +++ b/packages/logic/src/actions/turn/general/che_탈취.ts @@ -20,14 +20,12 @@ import { createNationPatchEffect, } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; +import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { ActionContextBase, ActionContextOptions } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { JosaUtil } from '@sammo-ts/common'; - -export interface SeizeArgs { - destCityId: number; -} +import { parseArgsWithSchema } from '../parseArgs.js'; export interface SeizeResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -40,6 +38,10 @@ export interface SeizeResolveContext< const ACTION_NAME = '탈취'; const ACTION_KEY = 'che_탈취'; +const ARGS_SCHEMA = z.object({ + destCityId: z.number(), +}); +export type SeizeArgs = z.infer; export class ActionResolver< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -194,9 +196,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): SeizeArgs | null { - const data = raw as Partial; - if (typeof data.destCityId !== 'number') return null; - return { destCityId: data.destCityId }; + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(ctx: ConstraintContext, _args: SeizeArgs): Constraint[] { @@ -240,5 +240,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '군사', reqArg: true, args: { destCityId: 0 }, + argsSchema: ARGS_SCHEMA, createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env), }; diff --git a/packages/logic/src/actions/turn/general/che_파괴.ts b/packages/logic/src/actions/turn/general/che_파괴.ts index a9af013..bd892f2 100644 --- a/packages/logic/src/actions/turn/general/che_파괴.ts +++ b/packages/logic/src/actions/turn/general/che_파괴.ts @@ -16,14 +16,12 @@ import type { } from '@sammo-ts/logic/actions/engine.js'; import { createGeneralPatchEffect, createCityPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; +import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { ActionContextBase, ActionContextOptions } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { JosaUtil } from '@sammo-ts/common'; - -export interface DestroyArgs { - destCityId: number; -} +import { parseArgsWithSchema } from '../parseArgs.js'; export interface DestroyResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -34,6 +32,10 @@ export interface DestroyResolveContext< const ACTION_NAME = '파괴'; const ACTION_KEY = 'che_파괴'; +const ARGS_SCHEMA = z.object({ + destCityId: z.number(), +}); +export type DestroyArgs = z.infer; export class ActionResolver< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -127,9 +129,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): DestroyArgs | null { - const data = raw as Partial; - if (typeof data.destCityId !== 'number') return null; - return { destCityId: data.destCityId }; + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(ctx: ConstraintContext, _args: DestroyArgs): Constraint[] { @@ -167,5 +167,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '군사', reqArg: true, args: { destCityId: 0 }, + argsSchema: ARGS_SCHEMA, createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env), }; diff --git a/packages/logic/src/actions/turn/general/che_헌납.ts b/packages/logic/src/actions/turn/general/che_헌납.ts index c763676..bc641ed 100644 --- a/packages/logic/src/actions/turn/general/che_헌납.ts +++ b/packages/logic/src/actions/turn/general/che_헌납.ts @@ -14,17 +14,19 @@ import type { } from '@sammo-ts/logic/actions/engine.js'; import { createGeneralPatchEffect, createNationPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat } from '@sammo-ts/logic/logging/types.js'; +import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; - -export interface DonateArgs { - isGold: boolean; - amount: number; -} +import { parseArgsWithSchema } from '../parseArgs.js'; const ACTION_NAME = '헌납'; const ACTION_KEY = 'che_헌납'; +const ARGS_SCHEMA = z.object({ + isGold: z.boolean(), + amount: z.number(), +}); +export type DonateArgs = z.infer; export class ActionResolver< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -91,9 +93,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): DonateArgs | null { - const data = raw as Partial; - if (typeof data.isGold !== 'boolean' || typeof data.amount !== 'number') return null; - return { isGold: data.isGold, amount: data.amount }; + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(_ctx: ConstraintContext, args: DonateArgs): Constraint[] { @@ -115,5 +115,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '내정', reqArg: true, args: { isGold: true, amount: 0 }, + argsSchema: ARGS_SCHEMA, createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(), }; diff --git a/packages/logic/src/actions/turn/general/che_화계.ts b/packages/logic/src/actions/turn/general/che_화계.ts index 4bf374f..99a6763 100644 --- a/packages/logic/src/actions/turn/general/che_화계.ts +++ b/packages/logic/src/actions/turn/general/che_화계.ts @@ -23,14 +23,12 @@ import type { } from '@sammo-ts/logic/actions/engine.js'; import { createCityPatchEffect, createGeneralPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; +import { z } from 'zod'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; import type { GeneralTurnCommandSpec } from './index.js'; import { clamp } from 'es-toolkit'; - -export interface FireAttackArgs { - destCityId: number; -} +import { parseArgsWithSchema } from '../parseArgs.js'; export interface FireAttackEnvironment { develCost: number; @@ -84,6 +82,10 @@ export interface FireAttackResult; const STAT_EXP_KEY = 'intel_exp'; const DEFAULT_MAX_PROB = 0.5; const INJURY_MAX = 80; @@ -357,14 +359,7 @@ export class ActionDefinition< } parseArgs(raw: unknown): FireAttackArgs | null { - if (!raw || typeof raw !== 'object') { - return null; - } - const destCityId = (raw as { destCityId?: unknown }).destCityId; - if (typeof destCityId !== 'number' || Number.isNaN(destCityId)) { - return null; - } - return { destCityId }; + return parseArgsWithSchema(ARGS_SCHEMA, raw); } buildConstraints(_ctx: ConstraintContext, _args: FireAttackArgs): Constraint[] { @@ -399,5 +394,6 @@ export const commandSpec: GeneralTurnCommandSpec = { category: '계략', reqArg: true, args: { destCityId: 0 }, + argsSchema: ARGS_SCHEMA, createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? [], env), };