커맨드의 arg 타입에 zod 강제.
This commit is contained in:
@@ -55,23 +55,22 @@ export interface ActionContextWorldRef {
|
|||||||
getTroopById(id: number): Troop | null;
|
getTroopById(id: number): Troop | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ActionContextOptions {
|
export interface ActionContextOptions<TArgs extends Record<string, unknown> = Record<string, unknown>> {
|
||||||
world: ActionContextWorldState;
|
world: ActionContextWorldState;
|
||||||
scenarioConfig: ScenarioConfig;
|
scenarioConfig: ScenarioConfig;
|
||||||
scenarioMeta?: ScenarioMeta;
|
scenarioMeta?: ScenarioMeta;
|
||||||
map?: MapDefinition;
|
map?: MapDefinition;
|
||||||
unitSet?: UnitSetDefinition;
|
unitSet?: UnitSetDefinition;
|
||||||
worldRef: ActionContextWorldRef | null;
|
worldRef: ActionContextWorldRef | null;
|
||||||
actionArgs: Record<string, unknown>;
|
actionArgs: TArgs;
|
||||||
createGeneralId: () => number;
|
createGeneralId: () => number;
|
||||||
createNationId: () => number;
|
createNationId: () => number;
|
||||||
seedBase: string;
|
seedBase: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 예약 턴 처리에서 커맨드별로 필요한 컨텍스트를 확장한다.
|
// 예약 턴 처리에서 커맨드별로 필요한 컨텍스트를 확장한다.
|
||||||
export type ActionContextBuilder = (
|
export type ActionContextBuilder<TArgs extends Record<string, unknown> = Record<string, unknown>> = {
|
||||||
base: ActionContextBase,
|
bivarianceHack(base: ActionContextBase, options: ActionContextOptions<TArgs>): ActionResolveContext | null;
|
||||||
options: ActionContextOptions
|
}['bivarianceHack'];
|
||||||
) => ActionResolveContext | null;
|
|
||||||
|
|
||||||
export const defaultActionContextBuilder: ActionContextBuilder = (base) => base;
|
export const defaultActionContextBuilder: ActionContextBuilder = (base) => base;
|
||||||
|
|||||||
@@ -3,16 +3,35 @@ import type { ZodType } from 'zod';
|
|||||||
import type { ActionContextBuilder } from './actionContext.js';
|
import type { ActionContextBuilder } from './actionContext.js';
|
||||||
import type { TurnCommandEnv } from './commandEnv.js';
|
import type { TurnCommandEnv } from './commandEnv.js';
|
||||||
|
|
||||||
export interface TurnCommandSpecBase<TKey extends string = string> {
|
export interface TurnCommandSpecWithArgs<TKey extends string = string> {
|
||||||
key: TKey;
|
key: TKey;
|
||||||
category: string;
|
category: string;
|
||||||
reqArg: boolean;
|
reqArg: true;
|
||||||
args: Record<string, unknown>;
|
args: Record<string, unknown>;
|
||||||
argsSchema?: ZodType<unknown>;
|
argsSchema: ZodType<Record<string, unknown>>;
|
||||||
createDefinition(env: TurnCommandEnv): GeneralActionDefinition;
|
createDefinition(env: TurnCommandEnv): GeneralActionDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TurnCommandSpecWithoutArgs<TKey extends string = string> {
|
||||||
|
key: TKey;
|
||||||
|
category: string;
|
||||||
|
reqArg: false;
|
||||||
|
args: Record<string, unknown>;
|
||||||
|
argsSchema?: undefined;
|
||||||
|
createDefinition(env: TurnCommandEnv): GeneralActionDefinition;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TurnCommandSpecBase<TKey extends string = string> =
|
||||||
|
| TurnCommandSpecWithArgs<TKey>
|
||||||
|
| TurnCommandSpecWithoutArgs<TKey>;
|
||||||
|
|
||||||
|
export type TurnCommandArgs<TSpec extends TurnCommandSpecBase> = TSpec extends {
|
||||||
|
argsSchema: ZodType<infer TArgs>;
|
||||||
|
}
|
||||||
|
? TArgs
|
||||||
|
: Record<string, never>;
|
||||||
|
|
||||||
export interface TurnCommandModule<TSpec extends TurnCommandSpecBase = TurnCommandSpecBase> {
|
export interface TurnCommandModule<TSpec extends TurnCommandSpecBase = TurnCommandSpecBase> {
|
||||||
commandSpec: TSpec;
|
commandSpec: TSpec;
|
||||||
actionContextBuilder?: ActionContextBuilder;
|
actionContextBuilder?: ActionContextBuilder<TurnCommandArgs<TSpec>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js';
|
import type { GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js';
|
||||||
import { ActionDefinition as RecruitActionDefinition } from './che_징병.js';
|
import { ARGS_SCHEMA, ActionDefinition as RecruitActionDefinition } from './che_징병.js';
|
||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||||
import type { GeneralTurnCommandSpec } from './index.js';
|
import type { GeneralTurnCommandSpec } from './index.js';
|
||||||
@@ -30,5 +30,6 @@ export const commandSpec: GeneralTurnCommandSpec = {
|
|||||||
crewType: 'number',
|
crewType: 'number',
|
||||||
amount: 'number',
|
amount: 'number',
|
||||||
},
|
},
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? []),
|
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? []),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ const DEFAULT_ATMOS = 40;
|
|||||||
const DEFAULT_MIN_POP = 30000;
|
const DEFAULT_MIN_POP = 30000;
|
||||||
const DEFAULT_TRUST = 50;
|
const DEFAULT_TRUST = 50;
|
||||||
const MIN_CREW = 100;
|
const MIN_CREW = 100;
|
||||||
const ARGS_SCHEMA = z.preprocess(
|
export const ARGS_SCHEMA = z.preprocess(
|
||||||
(raw) => {
|
(raw) => {
|
||||||
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
||||||
return raw;
|
return raw;
|
||||||
|
|||||||
@@ -12,10 +12,8 @@ import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.
|
|||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
import { z } from 'zod';
|
||||||
export interface ChangeFlagArgs {
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
colorType: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ACTION_NAME = '국기변경';
|
const ACTION_NAME = '국기변경';
|
||||||
|
|
||||||
@@ -55,6 +53,11 @@ const NATION_COLORS = [
|
|||||||
'#A9A9A9',
|
'#A9A9A9',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const ARGS_SCHEMA = z.object({
|
||||||
|
colorType: z.number().int().min(0).max(NATION_COLORS.length - 1),
|
||||||
|
});
|
||||||
|
export type ChangeFlagArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
export class ActionDefinition<
|
export class ActionDefinition<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
> implements GeneralActionDefinition<TriggerState, ChangeFlagArgs> {
|
> implements GeneralActionDefinition<TriggerState, ChangeFlagArgs> {
|
||||||
@@ -62,10 +65,7 @@ export class ActionDefinition<
|
|||||||
public readonly name = ACTION_NAME;
|
public readonly name = ACTION_NAME;
|
||||||
|
|
||||||
parseArgs(raw: unknown): ChangeFlagArgs | null {
|
parseArgs(raw: unknown): ChangeFlagArgs | null {
|
||||||
const data = raw as { colorType?: number };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
if (typeof data?.colorType !== 'number') return null;
|
|
||||||
if (data.colorType < 0 || data.colorType >= NATION_COLORS.length) return null;
|
|
||||||
return { colorType: data.colorType };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, _args: ChangeFlagArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, _args: ChangeFlagArgs): Constraint[] {
|
||||||
@@ -162,5 +162,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '국가',
|
category: '국가',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { colorType: 0 },
|
args: { colorType: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,10 +12,13 @@ import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.
|
|||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface ChangeNationNameArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
nationName: string;
|
nationName: z.string().trim().min(1).max(8),
|
||||||
}
|
});
|
||||||
|
export type ChangeNationNameArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
const ACTION_NAME = '국호변경';
|
const ACTION_NAME = '국호변경';
|
||||||
|
|
||||||
@@ -26,13 +29,7 @@ export class ActionDefinition<
|
|||||||
public readonly name = ACTION_NAME;
|
public readonly name = ACTION_NAME;
|
||||||
|
|
||||||
parseArgs(raw: unknown): ChangeNationNameArgs | null {
|
parseArgs(raw: unknown): ChangeNationNameArgs | null {
|
||||||
const data = raw as { nationName?: string };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
if (typeof data?.nationName !== 'string') return null;
|
|
||||||
|
|
||||||
const name = data.nationName.trim();
|
|
||||||
if (name.length < 1 || name.length > 8) return null;
|
|
||||||
|
|
||||||
return { nationName: name };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, args: ChangeNationNameArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, args: ChangeNationNameArgs): Constraint[] {
|
||||||
@@ -124,5 +121,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '국가',
|
category: '국가',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { nationName: '' },
|
args: { nationName: '' },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,10 +22,16 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'
|
|||||||
import { buildDefaultDiplomacy } from '../../../diplomacy/index.js';
|
import { buildDefaultDiplomacy } from '../../../diplomacy/index.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface RaidArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destNationId: number;
|
destNationId: z.preprocess(
|
||||||
}
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
|
z.number().int().positive()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
export type RaidArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
export interface RaidResolveContext<
|
export interface RaidResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -43,14 +49,6 @@ const PRE_REQ_TURN = 0;
|
|||||||
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
||||||
const TERM_REDUCE = 3;
|
const TERM_REDUCE = 3;
|
||||||
|
|
||||||
const parseNationId = (raw: unknown): number | null => {
|
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const value = Math.floor(raw);
|
|
||||||
return value > 0 ? value : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 급습 쿨타임 계산을 담당한다.
|
// 급습 쿨타임 계산을 담당한다.
|
||||||
export class CommandResolver<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
export class CommandResolver<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
||||||
private readonly pipeline: GeneralActionPipeline<TriggerState>;
|
private readonly pipeline: GeneralActionPipeline<TriggerState>;
|
||||||
@@ -173,12 +171,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseArgs(raw: unknown): RaidArgs | null {
|
parseArgs(raw: unknown): RaidArgs | null {
|
||||||
const data = raw as { destNationId?: unknown };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
const destNationId = parseNationId(data?.destNationId);
|
|
||||||
if (destNationId === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { destNationId };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, _args: RaidArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, _args: RaidArgs): Constraint[] {
|
||||||
@@ -199,7 +192,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 예약 턴 실행에 필요한 대상 국가/외교 정보를 구성한다.
|
// 예약 턴 실행에 필요한 대상 국가/외교 정보를 구성한다.
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<RaidArgs> = (base, options) => {
|
||||||
const destNationId = options.actionArgs.destNationId;
|
const destNationId = options.actionArgs.destNationId;
|
||||||
if (typeof destNationId !== 'number') {
|
if (typeof destNationId !== 'number') {
|
||||||
return null;
|
return null;
|
||||||
@@ -236,5 +229,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '외교',
|
category: '외교',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destNationId: 0 },
|
args: { destNationId: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? []),
|
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? []),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,12 +20,18 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'
|
|||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||||
import { clamp } from 'es-toolkit';
|
import { clamp } from 'es-toolkit';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface SeizureArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
isGold: boolean;
|
isGold: z.boolean(),
|
||||||
amount: number;
|
amount: z.preprocess(
|
||||||
destGeneralID: number;
|
(value) => (typeof value === 'number' ? Math.floor(value / 100) * 100 : value),
|
||||||
}
|
z.number().int().positive()
|
||||||
|
),
|
||||||
|
destGeneralID: z.number(),
|
||||||
|
});
|
||||||
|
export type SeizureArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
export interface SeizureResolveContext<
|
export interface SeizureResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -44,18 +50,13 @@ export class ActionDefinition<
|
|||||||
constructor(private readonly env: TurnCommandEnv) {}
|
constructor(private readonly env: TurnCommandEnv) {}
|
||||||
|
|
||||||
parseArgs(raw: unknown): SeizureArgs | null {
|
parseArgs(raw: unknown): SeizureArgs | null {
|
||||||
const data = raw as { isGold?: boolean; amount?: number; destGeneralID?: number };
|
const data = parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
if (typeof data?.isGold !== 'boolean') return null;
|
if (!data) {
|
||||||
if (typeof data?.amount !== 'number') return null;
|
return null;
|
||||||
if (typeof data?.destGeneralID !== 'number') return null;
|
}
|
||||||
|
|
||||||
const amount = Math.floor(data.amount / 100) * 100;
|
|
||||||
if (amount <= 0) return null;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isGold: data.isGold,
|
...data,
|
||||||
amount: clamp(amount, 100, this.env.maxResourceActionAmount ?? 10000),
|
amount: clamp(data.amount, 100, this.env.maxResourceActionAmount ?? 10000),
|
||||||
destGeneralID: data.destGeneralID,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +141,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<SeizureArgs> = (base, options) => {
|
||||||
const destGeneralId = options.actionArgs.destGeneralID;
|
const destGeneralId = options.actionArgs.destGeneralID;
|
||||||
if (typeof destGeneralId !== 'number') return null;
|
if (typeof destGeneralId !== 'number') return null;
|
||||||
|
|
||||||
@@ -161,5 +162,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '인사',
|
category: '인사',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { isGold: false, amount: 0, destGeneralID: 0 },
|
args: { isGold: false, amount: 0, destGeneralID: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env),
|
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,11 +20,14 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'
|
|||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||||
import { clamp } from 'es-toolkit';
|
import { clamp } from 'es-toolkit';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface MaterialAidArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destNationId: number;
|
destNationId: z.number(),
|
||||||
amountList: [number, number]; // [gold, rice]
|
amountList: z.tuple([z.number(), z.number()]),
|
||||||
}
|
});
|
||||||
|
export type MaterialAidArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
export interface MaterialAidResolveContext<
|
export interface MaterialAidResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -45,15 +48,7 @@ export class ActionDefinition<
|
|||||||
constructor(private readonly env: TurnCommandEnv) {}
|
constructor(private readonly env: TurnCommandEnv) {}
|
||||||
|
|
||||||
parseArgs(raw: unknown): MaterialAidArgs | null {
|
parseArgs(raw: unknown): MaterialAidArgs | null {
|
||||||
const data = raw as { destNationId?: number; amountList?: [number, number] };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
if (typeof data?.destNationId !== 'number') return null;
|
|
||||||
if (!Array.isArray(data.amountList) || data.amountList.length !== 2) return null;
|
|
||||||
if (typeof data.amountList[0] !== 'number' || typeof data.amountList[1] !== 'number') return null;
|
|
||||||
|
|
||||||
return {
|
|
||||||
destNationId: data.destNationId,
|
|
||||||
amountList: [data.amountList[0], data.amountList[1]],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, args: MaterialAidArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, args: MaterialAidArgs): Constraint[] {
|
||||||
@@ -187,7 +182,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<MaterialAidArgs> = (base, options) => {
|
||||||
const destNationId = options.actionArgs.destNationId;
|
const destNationId = options.actionArgs.destNationId;
|
||||||
if (typeof destNationId !== 'number') return null;
|
if (typeof destNationId !== 'number') return null;
|
||||||
|
|
||||||
@@ -208,5 +203,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '외교',
|
category: '외교',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destNationId: 0, amountList: [0, 0] },
|
args: { destNationId: 0, amountList: [0, 0] },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env),
|
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,11 +25,14 @@ import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionCo
|
|||||||
import { resolveTurnTermMinutes } from '@sammo-ts/logic/actions/turn/actionContextHelpers.js';
|
import { resolveTurnTermMinutes } from '@sammo-ts/logic/actions/turn/actionContextHelpers.js';
|
||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface AssignmentArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destGeneralId: number;
|
destGeneralId: z.number(),
|
||||||
destCityId: number;
|
destCityId: z.number(),
|
||||||
}
|
});
|
||||||
|
export type AssignmentArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
export interface AssignmentResolveContext<
|
export interface AssignmentResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -146,23 +149,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseArgs(raw: unknown): AssignmentArgs | null {
|
parseArgs(raw: unknown): AssignmentArgs | null {
|
||||||
if (!raw || typeof raw !== 'object') {
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const data = raw as {
|
|
||||||
destGeneralId?: unknown;
|
|
||||||
destCityId?: unknown;
|
|
||||||
};
|
|
||||||
if (typeof data.destGeneralId !== 'number') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (typeof data.destCityId !== 'number') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
destGeneralId: data.destGeneralId,
|
|
||||||
destCityId: data.destCityId,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(ctx: ConstraintContext, _args: AssignmentArgs): Constraint[] {
|
buildConstraints(ctx: ConstraintContext, _args: AssignmentArgs): Constraint[] {
|
||||||
@@ -188,7 +175,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 예약 턴 실행에 필요한 대상 장수/도시 컨텍스트를 구성한다.
|
// 예약 턴 실행에 필요한 대상 장수/도시 컨텍스트를 구성한다.
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<AssignmentArgs> = (base, options) => {
|
||||||
const destGeneralId = options.actionArgs.destGeneralId;
|
const destGeneralId = options.actionArgs.destGeneralId;
|
||||||
const destCityId = options.actionArgs.destCityId;
|
const destCityId = options.actionArgs.destCityId;
|
||||||
if (typeof destGeneralId !== 'number' || typeof destCityId !== 'number') {
|
if (typeof destGeneralId !== 'number' || typeof destCityId !== 'number') {
|
||||||
@@ -216,5 +203,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '인사',
|
category: '인사',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destGeneralId: 0, destCityId: 0 },
|
args: { destGeneralId: 0, destCityId: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition({}),
|
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition({}),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,10 +20,16 @@ import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionCo
|
|||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface MobilizePeopleArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destCityId: number;
|
destCityId: z.preprocess(
|
||||||
}
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
|
z.number().int().positive()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
export type MobilizePeopleArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
export interface MobilizePeopleResolveContext<
|
export interface MobilizePeopleResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -38,14 +44,6 @@ const PRE_REQ_TURN = 0;
|
|||||||
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
||||||
const DEFENCE_RATE = 0.8;
|
const DEFENCE_RATE = 0.8;
|
||||||
|
|
||||||
const parseCityId = (raw: unknown): number | null => {
|
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const value = Math.floor(raw);
|
|
||||||
return value > 0 ? value : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 백성동원 쿨타임 계산을 담당한다.
|
// 백성동원 쿨타임 계산을 담당한다.
|
||||||
export class CommandResolver<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
export class CommandResolver<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
||||||
private readonly pipeline: GeneralActionPipeline<TriggerState>;
|
private readonly pipeline: GeneralActionPipeline<TriggerState>;
|
||||||
@@ -151,12 +149,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseArgs(raw: unknown): MobilizePeopleArgs | null {
|
parseArgs(raw: unknown): MobilizePeopleArgs | null {
|
||||||
const data = raw as { destCityId?: unknown };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
const destCityId = parseCityId(data?.destCityId);
|
|
||||||
if (destCityId === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { destCityId };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, _args: MobilizePeopleArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, _args: MobilizePeopleArgs): Constraint[] {
|
||||||
@@ -174,7 +167,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 예약 턴 실행에 필요한 대상 도시/장수 정보를 구성한다.
|
// 예약 턴 실행에 필요한 대상 도시/장수 정보를 구성한다.
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<MobilizePeopleArgs> = (base, options) => {
|
||||||
const destCityId = options.actionArgs.destCityId;
|
const destCityId = options.actionArgs.destCityId;
|
||||||
if (typeof destCityId !== 'number') {
|
if (typeof destCityId !== 'number') {
|
||||||
return null;
|
return null;
|
||||||
@@ -200,5 +193,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '전략',
|
category: '전략',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destCityId: 0 },
|
args: { destCityId: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? []),
|
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? []),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,10 +19,16 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'
|
|||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js';
|
import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface TroopKickArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destGeneralId: number;
|
destGeneralId: z.preprocess(
|
||||||
}
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
|
z.number().int().positive()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
export type TroopKickArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
export interface TroopKickResolveContext<
|
export interface TroopKickResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -39,17 +45,7 @@ export class ActionDefinition<
|
|||||||
public readonly name = ACTION_NAME;
|
public readonly name = ACTION_NAME;
|
||||||
|
|
||||||
parseArgs(raw: unknown): TroopKickArgs | null {
|
parseArgs(raw: unknown): TroopKickArgs | null {
|
||||||
if (!raw || typeof raw !== 'object') {
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const data = raw as { destGeneralId?: unknown };
|
|
||||||
if (typeof data.destGeneralId !== 'number') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (data.destGeneralId <= 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { destGeneralId: data.destGeneralId };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(ctx: ConstraintContext, _args: TroopKickArgs): Constraint[] {
|
buildConstraints(ctx: ConstraintContext, _args: TroopKickArgs): Constraint[] {
|
||||||
@@ -98,7 +94,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<TroopKickArgs> = (base, options) => {
|
||||||
const destGeneralId = options.actionArgs.destGeneralId;
|
const destGeneralId = options.actionArgs.destGeneralId;
|
||||||
if (typeof destGeneralId !== 'number') {
|
if (typeof destGeneralId !== 'number') {
|
||||||
return null;
|
return null;
|
||||||
@@ -118,5 +114,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '인사',
|
category: '인사',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destGeneralId: 0 },
|
args: { destGeneralId: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,38 +15,25 @@ import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.
|
|||||||
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface NonAggressionProposalArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destNationId: number;
|
destNationId: z.preprocess(
|
||||||
year: number;
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
month: number;
|
z.number().int().positive()
|
||||||
}
|
),
|
||||||
|
year: z.preprocess((value) => (typeof value === 'number' ? Math.floor(value) : value), z.number().int().min(0)),
|
||||||
|
month: z.preprocess(
|
||||||
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
|
z.number().int().min(1).max(12)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
export type NonAggressionProposalArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
const ACTION_NAME = '불가침 제의';
|
const ACTION_NAME = '불가침 제의';
|
||||||
const MIN_TERM_MONTHS = 6;
|
const MIN_TERM_MONTHS = 6;
|
||||||
|
|
||||||
const parseNationId = (raw: unknown): number | null => {
|
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return raw > 0 ? Math.floor(raw) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseYear = (raw: unknown): number | null => {
|
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return raw >= 0 ? Math.floor(raw) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseMonth = (raw: unknown): number | null => {
|
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const month = Math.floor(raw);
|
|
||||||
return month >= 1 && month <= 12 ? month : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolveMonthIndex = (year: number, month: number): number => year * 12 + month - 1;
|
const resolveMonthIndex = (year: number, month: number): number => year * 12 + month - 1;
|
||||||
|
|
||||||
const requireMinimumTerm = (minMonths: number): Constraint => ({
|
const requireMinimumTerm = (minMonths: number): Constraint => ({
|
||||||
@@ -107,18 +94,7 @@ export class ActionDefinition<
|
|||||||
public readonly name = ACTION_NAME;
|
public readonly name = ACTION_NAME;
|
||||||
|
|
||||||
parseArgs(raw: unknown): NonAggressionProposalArgs | null {
|
parseArgs(raw: unknown): NonAggressionProposalArgs | null {
|
||||||
const data = raw as {
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
destNationId?: unknown;
|
|
||||||
year?: unknown;
|
|
||||||
month?: unknown;
|
|
||||||
};
|
|
||||||
const destNationId = parseNationId(data?.destNationId);
|
|
||||||
const year = parseYear(data?.year);
|
|
||||||
const month = parseMonth(data?.month);
|
|
||||||
if (destNationId === null || year === null || month === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { destNationId, year, month };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, _args: NonAggressionProposalArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, _args: NonAggressionProposalArgs): Constraint[] {
|
||||||
@@ -163,5 +139,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '외교',
|
category: '외교',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destNationId: 0, year: 0, month: 0 },
|
args: { destNationId: 0, year: 0, month: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,21 +15,20 @@ import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.
|
|||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface NonAggressionCancelProposalArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destNationId: number;
|
destNationId: z.preprocess(
|
||||||
}
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
|
z.number().int().positive()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
export type NonAggressionCancelProposalArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
const ACTION_NAME = '불가침 파기 제의';
|
const ACTION_NAME = '불가침 파기 제의';
|
||||||
const DIPLOMACY_NON_AGGRESSION = 7;
|
const DIPLOMACY_NON_AGGRESSION = 7;
|
||||||
|
|
||||||
const parseNationId = (raw: unknown): number | null => {
|
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return raw > 0 ? Math.floor(raw) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 불가침 파기 제의를 처리하는 국가 커맨드.
|
// 불가침 파기 제의를 처리하는 국가 커맨드.
|
||||||
export class ActionDefinition<
|
export class ActionDefinition<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -38,12 +37,7 @@ export class ActionDefinition<
|
|||||||
public readonly name = ACTION_NAME;
|
public readonly name = ACTION_NAME;
|
||||||
|
|
||||||
parseArgs(raw: unknown): NonAggressionCancelProposalArgs | null {
|
parseArgs(raw: unknown): NonAggressionCancelProposalArgs | null {
|
||||||
const data = raw as { destNationId?: unknown };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
const destNationId = parseNationId(data?.destNationId);
|
|
||||||
if (destNationId === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { destNationId };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, _args: NonAggressionCancelProposalArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, _args: NonAggressionCancelProposalArgs): Constraint[] {
|
||||||
@@ -81,5 +75,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '외교',
|
category: '외교',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destNationId: 0 },
|
args: { destNationId: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,10 +21,16 @@ import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionCo
|
|||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface DeclareWarArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destNationId: number;
|
destNationId: z.preprocess(
|
||||||
}
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
|
z.number().int().positive()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
export type DeclareWarArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
// DeclareWarResolveContext is not used anymore as it was replaced by inline type in ActionDefinition
|
// DeclareWarResolveContext is not used anymore as it was replaced by inline type in ActionDefinition
|
||||||
|
|
||||||
@@ -33,13 +39,6 @@ const ACTION_NAME = '선전포고';
|
|||||||
const DIPLOMACY_DECLARE = 1;
|
const DIPLOMACY_DECLARE = 1;
|
||||||
const DECLARE_TERM = 24;
|
const DECLARE_TERM = 24;
|
||||||
|
|
||||||
const parseNationId = (raw: unknown): number | null => {
|
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return raw > 0 ? Math.floor(raw) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ActionDefinition<
|
export class ActionDefinition<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
> implements GeneralActionDefinition<
|
> implements GeneralActionDefinition<
|
||||||
@@ -51,12 +50,7 @@ export class ActionDefinition<
|
|||||||
public readonly name = ACTION_NAME;
|
public readonly name = ACTION_NAME;
|
||||||
|
|
||||||
parseArgs(raw: unknown): DeclareWarArgs | null {
|
parseArgs(raw: unknown): DeclareWarArgs | null {
|
||||||
const data = raw as { destNationId?: unknown };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
const destNationId = parseNationId(data?.destNationId);
|
|
||||||
if (destNationId === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { destNationId };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, _args: DeclareWarArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, _args: DeclareWarArgs): Constraint[] {
|
||||||
@@ -164,7 +158,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 예약 턴 실행에 필요한 대상 국가 정보를 구성한다.
|
// 예약 턴 실행에 필요한 대상 국가 정보를 구성한다.
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<DeclareWarArgs> = (base, options) => {
|
||||||
const destNationId = options.actionArgs.destNationId;
|
const destNationId = options.actionArgs.destNationId;
|
||||||
if (typeof destNationId !== 'number') {
|
if (typeof destNationId !== 'number') {
|
||||||
return null;
|
return null;
|
||||||
@@ -188,5 +182,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '외교',
|
category: '외교',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destNationId: 0 },
|
args: { destNationId: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,10 +22,16 @@ import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionCo
|
|||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface FloodArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destCityId: number;
|
destCityId: z.preprocess(
|
||||||
}
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
|
z.number().int().positive()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
export type FloodArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
export interface FloodResolveContext<
|
export interface FloodResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -42,14 +48,6 @@ const PRE_REQ_TURN = 2;
|
|||||||
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
||||||
const DAMAGE_RATE = 0.2;
|
const DAMAGE_RATE = 0.2;
|
||||||
|
|
||||||
const parseCityId = (raw: unknown): number | null => {
|
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const value = Math.floor(raw);
|
|
||||||
return value > 0 ? value : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 수몰 쿨타임 계산을 담당한다.
|
// 수몰 쿨타임 계산을 담당한다.
|
||||||
export class CommandResolver<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
export class CommandResolver<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
|
||||||
private readonly pipeline: GeneralActionPipeline<TriggerState>;
|
private readonly pipeline: GeneralActionPipeline<TriggerState>;
|
||||||
@@ -176,12 +174,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseArgs(raw: unknown): FloodArgs | null {
|
parseArgs(raw: unknown): FloodArgs | null {
|
||||||
const data = raw as { destCityId?: unknown };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
const destCityId = parseCityId(data?.destCityId);
|
|
||||||
if (destCityId === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { destCityId };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, _args: FloodArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, _args: FloodArgs): Constraint[] {
|
||||||
@@ -203,7 +196,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 예약 턴 실행에 필요한 대상 도시/장수 정보를 구성한다.
|
// 예약 턴 실행에 필요한 대상 도시/장수 정보를 구성한다.
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<FloodArgs> = (base, options) => {
|
||||||
const destCityId = options.actionArgs.destCityId;
|
const destCityId = options.actionArgs.destCityId;
|
||||||
if (typeof destCityId !== 'number') {
|
if (typeof destCityId !== 'number') {
|
||||||
return null;
|
return null;
|
||||||
@@ -234,5 +227,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '전략',
|
category: '전략',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destCityId: 0 },
|
args: { destCityId: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? []),
|
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? []),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,10 +22,16 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'
|
|||||||
import { buildDefaultDiplomacy, DIPLOMACY_STATE } from '../../../diplomacy/index.js';
|
import { buildDefaultDiplomacy, DIPLOMACY_STATE } from '../../../diplomacy/index.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface DegradeRelationsArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destNationId: number;
|
destNationId: z.preprocess(
|
||||||
}
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
|
z.number().int().positive()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
export type DegradeRelationsArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
export interface DegradeRelationsResolveContext<
|
export interface DegradeRelationsResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -42,14 +48,6 @@ const DEFAULT_GLOBAL_DELAY = 9;
|
|||||||
const PRE_REQ_TURN = 0;
|
const PRE_REQ_TURN = 0;
|
||||||
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
||||||
|
|
||||||
const parseNationId = (raw: unknown): number | null => {
|
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const value = Math.floor(raw);
|
|
||||||
return value > 0 ? value : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolveNextTerm = (state: number, term: number): number => (state === DIPLOMACY_STATE.WAR ? 3 : term + 3);
|
const resolveNextTerm = (state: number, term: number): number => (state === DIPLOMACY_STATE.WAR ? 3 : term + 3);
|
||||||
|
|
||||||
// 이호경식 쿨타임 계산을 담당한다.
|
// 이호경식 쿨타임 계산을 담당한다.
|
||||||
@@ -180,12 +178,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseArgs(raw: unknown): DegradeRelationsArgs | null {
|
parseArgs(raw: unknown): DegradeRelationsArgs | null {
|
||||||
const data = raw as { destNationId?: unknown };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
const destNationId = parseNationId(data?.destNationId);
|
|
||||||
if (destNationId === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { destNationId };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, _args: DegradeRelationsArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, _args: DegradeRelationsArgs): Constraint[] {
|
||||||
@@ -209,7 +202,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 예약 턴 실행에 필요한 대상 국가/외교 정보를 구성한다.
|
// 예약 턴 실행에 필요한 대상 국가/외교 정보를 구성한다.
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<DegradeRelationsArgs> = (base, options) => {
|
||||||
const destNationId = options.actionArgs.destNationId;
|
const destNationId = options.actionArgs.destNationId;
|
||||||
if (typeof destNationId !== 'number') {
|
if (typeof destNationId !== 'number') {
|
||||||
return null;
|
return null;
|
||||||
@@ -246,5 +239,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '외교',
|
category: '외교',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destNationId: 0 },
|
args: { destNationId: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? []),
|
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? []),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,20 +16,19 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'
|
|||||||
import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
import { defaultActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface StopWarProposalArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destNationId: number;
|
destNationId: z.preprocess(
|
||||||
}
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
|
z.number().int().positive()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
export type StopWarProposalArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
const ACTION_NAME = '종전 제의';
|
const ACTION_NAME = '종전 제의';
|
||||||
|
|
||||||
const parseNationId = (raw: unknown): number | null => {
|
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return raw > 0 ? Math.floor(raw) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 종전 제의를 처리하는 국가 커맨드.
|
// 종전 제의를 처리하는 국가 커맨드.
|
||||||
export class ActionDefinition<
|
export class ActionDefinition<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -38,12 +37,7 @@ export class ActionDefinition<
|
|||||||
public readonly name = ACTION_NAME;
|
public readonly name = ACTION_NAME;
|
||||||
|
|
||||||
parseArgs(raw: unknown): StopWarProposalArgs | null {
|
parseArgs(raw: unknown): StopWarProposalArgs | null {
|
||||||
const data = raw as { destNationId?: unknown };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
const destNationId = parseNationId(data?.destNationId);
|
|
||||||
if (destNationId === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { destNationId };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, _args: StopWarProposalArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, _args: StopWarProposalArgs): Constraint[] {
|
||||||
@@ -84,5 +78,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '외교',
|
category: '외교',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destNationId: 0 },
|
args: { destNationId: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,10 +20,13 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'
|
|||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
import type { MapDefinition } from '@sammo-ts/logic/world/types.js';
|
import type { MapDefinition } from '@sammo-ts/logic/world/types.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface MoveCapitalArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destCityID: number;
|
destCityID: z.number(),
|
||||||
}
|
});
|
||||||
|
export type MoveCapitalArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
export interface MoveCapitalResolveContext<
|
export interface MoveCapitalResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -71,9 +74,7 @@ export class ActionDefinition<
|
|||||||
constructor(private readonly env: TurnCommandEnv) {}
|
constructor(private readonly env: TurnCommandEnv) {}
|
||||||
|
|
||||||
parseArgs(raw: unknown): MoveCapitalArgs | null {
|
parseArgs(raw: unknown): MoveCapitalArgs | null {
|
||||||
const data = raw as { destCityID?: number };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
if (typeof data?.destCityID !== 'number') return null;
|
|
||||||
return { destCityID: data.destCityID };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, args: MoveCapitalArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, args: MoveCapitalArgs): Constraint[] {
|
||||||
@@ -194,7 +195,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<MoveCapitalArgs> = (base, options) => {
|
||||||
const destCityId = options.actionArgs.destCityID;
|
const destCityId = options.actionArgs.destCityID;
|
||||||
if (typeof destCityId !== 'number') return null;
|
if (typeof destCityId !== 'number') return null;
|
||||||
|
|
||||||
@@ -217,5 +218,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '국가',
|
category: '국가',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destCityID: 0 },
|
args: { destCityID: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env),
|
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,10 +21,16 @@ import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionCo
|
|||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface ScorchedEarthArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destCityId: number;
|
destCityId: z.preprocess(
|
||||||
}
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
|
z.number().int().positive()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
export type ScorchedEarthArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
export interface ScorchedEarthResolveContext<
|
export interface ScorchedEarthResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -37,14 +43,6 @@ const ACTION_NAME = '초토화';
|
|||||||
const PRE_REQ_TURN = 2;
|
const PRE_REQ_TURN = 2;
|
||||||
const POST_REQ_TURN = 24;
|
const POST_REQ_TURN = 24;
|
||||||
|
|
||||||
const parseCityId = (raw: unknown): number | null => {
|
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const value = Math.floor(raw);
|
|
||||||
return value > 0 ? value : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const requireNoDiplomacyLimit = (): Constraint => ({
|
const requireNoDiplomacyLimit = (): Constraint => ({
|
||||||
name: 'requireNoDiplomacyLimit',
|
name: 'requireNoDiplomacyLimit',
|
||||||
requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []),
|
requires: (ctx) => (ctx.nationId !== undefined ? [{ kind: 'nation', id: ctx.nationId }] : []),
|
||||||
@@ -115,12 +113,7 @@ export class ActionDefinition<
|
|||||||
public readonly name = ACTION_NAME;
|
public readonly name = ACTION_NAME;
|
||||||
|
|
||||||
parseArgs(raw: unknown): ScorchedEarthArgs | null {
|
parseArgs(raw: unknown): ScorchedEarthArgs | null {
|
||||||
const data = raw as { destCityId?: unknown };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
const destCityId = parseCityId(data?.destCityId);
|
|
||||||
if (destCityId === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { destCityId };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, args: ScorchedEarthArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, args: ScorchedEarthArgs): Constraint[] {
|
||||||
@@ -247,7 +240,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 예약 턴 실행에 필요한 대상 도시 정보를 구성한다.
|
// 예약 턴 실행에 필요한 대상 도시 정보를 구성한다.
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<ScorchedEarthArgs> = (base, options) => {
|
||||||
const destCityId = options.actionArgs.destCityId;
|
const destCityId = options.actionArgs.destCityId;
|
||||||
if (typeof destCityId !== 'number') {
|
if (typeof destCityId !== 'number') {
|
||||||
return null;
|
return null;
|
||||||
@@ -273,5 +266,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '특수',
|
category: '특수',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destCityId: 0 },
|
args: { destCityId: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,12 +25,15 @@ import type { NationTurnCommandSpec } from './index.js';
|
|||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||||
import { clamp } from 'es-toolkit';
|
import { clamp } from 'es-toolkit';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface AwardArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
isGold: boolean;
|
isGold: z.boolean(),
|
||||||
amount: number;
|
amount: z.number(),
|
||||||
destGeneralId: number;
|
destGeneralId: z.number(),
|
||||||
}
|
});
|
||||||
|
export type AwardArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
export interface AwardResolveContext<
|
export interface AwardResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -163,21 +166,8 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseArgs(raw: unknown): AwardArgs | null {
|
parseArgs(raw: unknown): AwardArgs | null {
|
||||||
if (!raw || typeof raw !== 'object') {
|
const data = parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
return null;
|
if (!data) {
|
||||||
}
|
|
||||||
const data = raw as {
|
|
||||||
isGold?: unknown;
|
|
||||||
amount?: unknown;
|
|
||||||
destGeneralId?: unknown;
|
|
||||||
};
|
|
||||||
if (typeof data.isGold !== 'boolean') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (typeof data.amount !== 'number' || Number.isNaN(data.amount)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (typeof data.destGeneralId !== 'number') {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const amount = this.command.normalizeAmount(data.amount);
|
const amount = this.command.normalizeAmount(data.amount);
|
||||||
@@ -185,9 +175,8 @@ export class ActionDefinition<
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
isGold: data.isGold,
|
...data,
|
||||||
amount,
|
amount,
|
||||||
destGeneralId: data.destGeneralId,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +220,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 예약 턴 실행에 필요한 대상 장수를 주입한다.
|
// 예약 턴 실행에 필요한 대상 장수를 주입한다.
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<AwardArgs> = (base, options) => {
|
||||||
const destGeneralId = options.actionArgs.destGeneralId;
|
const destGeneralId = options.actionArgs.destGeneralId;
|
||||||
if (typeof destGeneralId !== 'number') {
|
if (typeof destGeneralId !== 'number') {
|
||||||
return null;
|
return null;
|
||||||
@@ -251,6 +240,7 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '인사',
|
category: '인사',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { isGold: true, amount: 1, destGeneralId: 0 },
|
args: { isGold: true, amount: 1, destGeneralId: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (env: TurnCommandEnv) => {
|
createDefinition: (env: TurnCommandEnv) => {
|
||||||
const maxAmount =
|
const maxAmount =
|
||||||
env.maxResourceActionAmount > 0 ? env.maxResourceActionAmount : Math.max(env.baseGold, env.baseRice, 1000);
|
env.maxResourceActionAmount > 0 ? env.maxResourceActionAmount : Math.max(env.baseGold, env.baseRice, 1000);
|
||||||
|
|||||||
@@ -21,11 +21,10 @@ import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionCo
|
|||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface CounterStrategyArgs {
|
export type CounterStrategyArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
destNationId: number;
|
|
||||||
commandType: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CounterStrategyResolveContext<
|
export interface CounterStrategyResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -40,7 +39,18 @@ const DEFAULT_GLOBAL_DELAY = 8;
|
|||||||
const PRE_REQ_TURN = 1;
|
const PRE_REQ_TURN = 1;
|
||||||
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
||||||
|
|
||||||
const STRATEGIC_COMMANDS: Record<string, string> = {
|
const STRATEGIC_COMMAND_KEYS = [
|
||||||
|
'che_필사즉생',
|
||||||
|
'che_백성동원',
|
||||||
|
'che_수몰',
|
||||||
|
'che_허보',
|
||||||
|
'che_의병모집',
|
||||||
|
'che_이호경식',
|
||||||
|
'che_급습',
|
||||||
|
'che_피장파장',
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const STRATEGIC_COMMANDS: Record<(typeof STRATEGIC_COMMAND_KEYS)[number], string> = {
|
||||||
che_필사즉생: '필사즉생',
|
che_필사즉생: '필사즉생',
|
||||||
che_백성동원: '백성동원',
|
che_백성동원: '백성동원',
|
||||||
che_수몰: '수몰',
|
che_수몰: '수몰',
|
||||||
@@ -51,26 +61,15 @@ const STRATEGIC_COMMANDS: Record<string, string> = {
|
|||||||
che_피장파장: '피장파장',
|
che_피장파장: '피장파장',
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseNationId = (raw: unknown): number | null => {
|
const ARGS_SCHEMA = z.object({
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
destNationId: z.preprocess(
|
||||||
return null;
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
}
|
z.number().int().positive()
|
||||||
const value = Math.floor(raw);
|
),
|
||||||
return value > 0 ? value : null;
|
commandType: z
|
||||||
};
|
.enum(STRATEGIC_COMMAND_KEYS)
|
||||||
|
.refine((value) => value !== 'che_피장파장', '같은 전략은 선택할 수 없습니다.'),
|
||||||
const parseCommandType = (raw: unknown): string | null => {
|
});
|
||||||
if (typeof raw !== 'string') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(STRATEGIC_COMMANDS, raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (raw === 'che_피장파장') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return raw;
|
|
||||||
};
|
|
||||||
|
|
||||||
const requireCommandType = (): Constraint => ({
|
const requireCommandType = (): Constraint => ({
|
||||||
name: 'requireStrategicCommandType',
|
name: 'requireStrategicCommandType',
|
||||||
@@ -209,13 +208,7 @@ export class ActionDefinition<
|
|||||||
private readonly resolver = new ActionResolver<TriggerState>();
|
private readonly resolver = new ActionResolver<TriggerState>();
|
||||||
|
|
||||||
parseArgs(raw: unknown): CounterStrategyArgs | null {
|
parseArgs(raw: unknown): CounterStrategyArgs | null {
|
||||||
const data = raw as { destNationId?: unknown; commandType?: unknown };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
const destNationId = parseNationId(data?.destNationId);
|
|
||||||
const commandType = parseCommandType(data?.commandType);
|
|
||||||
if (destNationId === null || commandType === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { destNationId, commandType };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, _args: CounterStrategyArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, _args: CounterStrategyArgs): Constraint[] {
|
||||||
@@ -237,7 +230,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 예약 턴 실행에 필요한 대상 국가/장수 정보를 구성한다.
|
// 예약 턴 실행에 필요한 대상 국가/장수 정보를 구성한다.
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<CounterStrategyArgs> = (base, options) => {
|
||||||
const destNationId = options.actionArgs.destNationId;
|
const destNationId = options.actionArgs.destNationId;
|
||||||
if (typeof destNationId !== 'number') {
|
if (typeof destNationId !== 'number') {
|
||||||
return null;
|
return null;
|
||||||
@@ -266,5 +259,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '전략',
|
category: '전략',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destNationId: 0, commandType: '' },
|
args: { destNationId: 0, commandType: '' },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,10 +22,16 @@ import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionCo
|
|||||||
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js';
|
||||||
import { JosaUtil } from '@sammo-ts/common';
|
import { JosaUtil } from '@sammo-ts/common';
|
||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
|
|
||||||
export interface DeceptionArgs {
|
const ARGS_SCHEMA = z.object({
|
||||||
destCityId: number;
|
destCityId: z.preprocess(
|
||||||
}
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
|
z.number().int().positive()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
export type DeceptionArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
|
|
||||||
export interface DeceptionResolveContext<
|
export interface DeceptionResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -42,14 +48,6 @@ const DEFAULT_GLOBAL_DELAY = 9;
|
|||||||
const PRE_REQ_TURN = 1;
|
const PRE_REQ_TURN = 1;
|
||||||
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
const EXP_DED_GAIN = 5 * (PRE_REQ_TURN + 1);
|
||||||
|
|
||||||
const parseCityId = (raw: unknown): number | null => {
|
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const value = Math.floor(raw);
|
|
||||||
return value > 0 ? value : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const pickMoveCityId = (rng: GeneralActionResolveContext['rng'], destCityId: number, candidates: City[]): number => {
|
const pickMoveCityId = (rng: GeneralActionResolveContext['rng'], destCityId: number, candidates: City[]): number => {
|
||||||
if (candidates.length === 0) {
|
if (candidates.length === 0) {
|
||||||
return destCityId;
|
return destCityId;
|
||||||
@@ -183,12 +181,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
parseArgs(raw: unknown): DeceptionArgs | null {
|
parseArgs(raw: unknown): DeceptionArgs | null {
|
||||||
const data = raw as { destCityId?: unknown };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
const destCityId = parseCityId(data?.destCityId);
|
|
||||||
if (destCityId === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { destCityId };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, _args: DeceptionArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, _args: DeceptionArgs): Constraint[] {
|
||||||
@@ -210,7 +203,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 예약 턴 실행에 필요한 대상 도시/장수 정보를 구성한다.
|
// 예약 턴 실행에 필요한 대상 도시/장수 정보를 구성한다.
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<DeceptionArgs> = (base, options) => {
|
||||||
const destCityId = options.actionArgs.destCityId;
|
const destCityId = options.actionArgs.destCityId;
|
||||||
if (typeof destCityId !== 'number') {
|
if (typeof destCityId !== 'number') {
|
||||||
return null;
|
return null;
|
||||||
@@ -247,5 +240,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '전략',
|
category: '전략',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destCityId: 0 },
|
args: { destCityId: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? []),
|
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env.generalActionModules ?? []),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,11 +25,8 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'
|
|||||||
import type { NationTurnCommandSpec } from './index.js';
|
import type { NationTurnCommandSpec } from './index.js';
|
||||||
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js';
|
||||||
import { clamp } from 'es-toolkit';
|
import { clamp } from 'es-toolkit';
|
||||||
|
import { z } from 'zod';
|
||||||
export interface PopulationMoveArgs {
|
import { parseArgsWithSchema } from '../parseArgs.js';
|
||||||
destCityId: number;
|
|
||||||
amount: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PopulationMoveResolveContext<
|
export interface PopulationMoveResolveContext<
|
||||||
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
TriggerState extends GeneralTriggerState = GeneralTriggerState,
|
||||||
@@ -42,24 +39,17 @@ const ACTION_NAME = '인구이동';
|
|||||||
const AMOUNT_LIMIT = 100000;
|
const AMOUNT_LIMIT = 100000;
|
||||||
const MIN_AVAILABLE_RECRUIT_POP = 30000;
|
const MIN_AVAILABLE_RECRUIT_POP = 30000;
|
||||||
|
|
||||||
const parseCityId = (raw: unknown): number | null => {
|
const ARGS_SCHEMA = z.object({
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
destCityId: z.preprocess(
|
||||||
return null;
|
(value) => (typeof value === 'number' ? Math.floor(value) : value),
|
||||||
}
|
z.number().int().positive()
|
||||||
const value = Math.floor(raw);
|
),
|
||||||
return value > 0 ? value : null;
|
amount: z.preprocess(
|
||||||
};
|
(value) => (typeof value === 'number' ? clamp(Math.floor(value), 0, AMOUNT_LIMIT) : value),
|
||||||
|
z.number().int().min(0).max(AMOUNT_LIMIT)
|
||||||
const parseAmount = (raw: unknown): number | null => {
|
),
|
||||||
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
|
});
|
||||||
return null;
|
export type PopulationMoveArgs = z.infer<typeof ARGS_SCHEMA>;
|
||||||
}
|
|
||||||
const value = Math.floor(raw);
|
|
||||||
if (value < 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return clamp(value, 0, AMOUNT_LIMIT);
|
|
||||||
};
|
|
||||||
|
|
||||||
const calcCost = (develCost: number, amount: number): number => Math.round((develCost * amount) / 10000);
|
const calcCost = (develCost: number, amount: number): number => Math.round((develCost * amount) / 10000);
|
||||||
|
|
||||||
@@ -72,13 +62,7 @@ export class ActionDefinition<
|
|||||||
constructor(private readonly env: TurnCommandEnv) {}
|
constructor(private readonly env: TurnCommandEnv) {}
|
||||||
|
|
||||||
parseArgs(raw: unknown): PopulationMoveArgs | null {
|
parseArgs(raw: unknown): PopulationMoveArgs | null {
|
||||||
const data = raw as { destCityId?: unknown; amount?: unknown };
|
return parseArgsWithSchema(ARGS_SCHEMA, raw);
|
||||||
const destCityId = parseCityId(data?.destCityId);
|
|
||||||
const amount = parseAmount(data?.amount);
|
|
||||||
if (destCityId === null || amount === null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return { destCityId, amount };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildConstraints(_ctx: ConstraintContext, args: PopulationMoveArgs): Constraint[] {
|
buildConstraints(_ctx: ConstraintContext, args: PopulationMoveArgs): Constraint[] {
|
||||||
@@ -159,7 +143,7 @@ export class ActionDefinition<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actionContextBuilder: ActionContextBuilder = (base, options) => {
|
export const actionContextBuilder: ActionContextBuilder<PopulationMoveArgs> = (base, options) => {
|
||||||
const destCityId = options.actionArgs.destCityId;
|
const destCityId = options.actionArgs.destCityId;
|
||||||
if (typeof destCityId !== 'number') {
|
if (typeof destCityId !== 'number') {
|
||||||
return null;
|
return null;
|
||||||
@@ -185,5 +169,6 @@ export const commandSpec: NationTurnCommandSpec = {
|
|||||||
category: '특수',
|
category: '특수',
|
||||||
reqArg: true,
|
reqArg: true,
|
||||||
args: { destCityId: 0, amount: 0 },
|
args: { destCityId: 0, amount: 0 },
|
||||||
|
argsSchema: ARGS_SCHEMA,
|
||||||
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env),
|
createDefinition: (env: TurnCommandEnv) => new ActionDefinition(env),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ describe('Nation Missing Actions', () => {
|
|||||||
setupDiplomacy(view, nation.id, destNation.id, 2);
|
setupDiplomacy(view, nation.id, destNation.id, 2);
|
||||||
|
|
||||||
const definition = new CounterStrategyAction();
|
const definition = new CounterStrategyAction();
|
||||||
const args = { destNationId: destNation.id, commandType: 'che_허보' };
|
const args = { destNationId: destNation.id, commandType: 'che_허보' } as const;
|
||||||
const ctx: ConstraintContext = {
|
const ctx: ConstraintContext = {
|
||||||
actorId: general.id,
|
actorId: general.id,
|
||||||
nationId: nation.id,
|
nationId: nation.id,
|
||||||
@@ -327,7 +327,7 @@ describe('Nation Missing Actions', () => {
|
|||||||
addLog: () => {},
|
addLog: () => {},
|
||||||
} as any,
|
} as any,
|
||||||
{ now: new Date(), schedule },
|
{ now: new Date(), schedule },
|
||||||
{ destNationId: destNation.id, commandType: 'che_허보' }
|
{ destNationId: destNation.id, commandType: 'che_허보' } as const
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(resolution.general.experience).toBeGreaterThan(100);
|
expect(resolution.general.experience).toBeGreaterThan(100);
|
||||||
|
|||||||
Reference in New Issue
Block a user