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:
@@ -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),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user