feat: add Zod schema validation for command arguments in various action modules

- Introduced `argsSchema` property in `TurnCommandSpecBase` to enforce argument validation using Zod.
- Refactored argument parsing in multiple action modules (e.g., `che_NPC능동`, `che_강행`, `che_건국`, etc.) to utilize Zod schemas for improved type safety and validation.
- Removed redundant manual checks for argument types and replaced them with Zod's validation mechanisms.
- Enhanced code maintainability and readability by centralizing argument validation logic.
This commit is contained in:
2026-01-15 15:27:20 +00:00
parent d1b2076a32
commit 237e7e73cc
18 changed files with 159 additions and 162 deletions
@@ -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<TKey extends string = string> {
category: string;
reqArg: boolean;
args: Record<string, unknown>;
argsSchema?: ZodType<unknown>;
createDefinition(env: TurnCommandEnv): GeneralActionDefinition;
}
@@ -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<TriggerState extends GeneralTriggerState = GeneralTriggerState> =
GeneralActionResolveContext<TriggerState> & {
map?: MapDefinition;
@@ -29,12 +24,13 @@ export type NPCSelfResolveContext<TriggerState extends GeneralTriggerState = Gen
const ACTION_NAME = 'NPC능동';
const ACTION_KEY = 'che_NPC능동';
const NPC_SELF_ARGS_SCHEMA = z.discriminatedUnion('optionText', [
const ARGS_SCHEMA = z.discriminatedUnion('optionText', [
z.object({
optionText: z.literal('순간이동'),
destCityId: z.number(),
}),
]);
export type NPCSelfArgs = z.infer<typeof ARGS_SCHEMA>;
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(),
};
@@ -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<TriggerState> {
@@ -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<typeof ARGS_SCHEMA>;
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(),
};
@@ -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<typeof ARGS_SCHEMA>;
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<string, unknown>;
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(),
};
@@ -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<typeof ARGS_SCHEMA>;
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<string, unknown>;
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(),
};
@@ -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<typeof ARGS_SCHEMA>;
const differentNationDestGeneral = (): Constraint => ({
name: 'DifferentNationDestGeneral',
@@ -145,9 +147,7 @@ export class ActionDefinition<
}
parseArgs(raw: unknown): EmployArgs | null {
const data = raw as Partial<EmployArgs>;
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),
};
@@ -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<typeof ARGS_SCHEMA>;
export interface AcceptScoutResolveContext<
TriggerState extends GeneralTriggerState = GeneralTriggerState,
@@ -180,9 +182,7 @@ export class ActionDefinition<
}
parseArgs(raw: unknown): AcceptScoutArgs | null {
const args = raw as Partial<AcceptScoutArgs>;
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),
};
@@ -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<typeof ARGS_SCHEMA>;
export class ActionResolver<
TriggerState extends GeneralTriggerState = GeneralTriggerState,
@@ -130,9 +132,7 @@ export class ActionDefinition<
}
parseArgs(raw: unknown): AgitateArgs | null {
const data = raw as Partial<AgitateArgs>;
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),
};
@@ -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<string, unknown> =>
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<typeof ARGS_SCHEMA>;
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 }),
};
@@ -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<TriggerState> {
@@ -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<typeof ARGS_SCHEMA>;
// 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(),
};
@@ -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<typeof ARGS_SCHEMA>;
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(),
};
@@ -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<string, unknown> =>
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<string, unknown>;
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<typeof ARGS_SCHEMA>;
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 ?? [], {}),
};
@@ -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<typeof ARGS_SCHEMA>;
export class ActionResolver<
TriggerState extends GeneralTriggerState = GeneralTriggerState,
@@ -126,9 +128,7 @@ export class ActionDefinition<
}
parseArgs(raw: unknown): SpyArgs | null {
const data = raw as Partial<SpyArgs>;
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(),
};
@@ -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<typeof ARGS_SCHEMA>;
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 ?? []),
};
@@ -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<typeof ARGS_SCHEMA>;
export class ActionResolver<
TriggerState extends GeneralTriggerState = GeneralTriggerState,
@@ -194,9 +196,7 @@ export class ActionDefinition<
}
parseArgs(raw: unknown): SeizeArgs | null {
const data = raw as Partial<SeizeArgs>;
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),
};
@@ -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<typeof ARGS_SCHEMA>;
export class ActionResolver<
TriggerState extends GeneralTriggerState = GeneralTriggerState,
@@ -127,9 +129,7 @@ export class ActionDefinition<
}
parseArgs(raw: unknown): DestroyArgs | null {
const data = raw as Partial<DestroyArgs>;
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),
};
@@ -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<typeof ARGS_SCHEMA>;
export class ActionResolver<
TriggerState extends GeneralTriggerState = GeneralTriggerState,
@@ -91,9 +93,7 @@ export class ActionDefinition<
}
parseArgs(raw: unknown): DonateArgs | null {
const data = raw as Partial<DonateArgs>;
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(),
};
@@ -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<TriggerState extends GeneralTriggerState = Gen
const ACTION_NAME = '화계';
const ACTION_KEY = '계략';
const ARGS_SCHEMA = z.object({
destCityId: z.number(),
});
export type FireAttackArgs = z.infer<typeof ARGS_SCHEMA>;
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),
};