feat: add non-aggression cancel actions and related diplomacy constraints

This commit is contained in:
2026-01-03 06:09:10 +00:00
parent 11ffdfef0e
commit b628b32968
6 changed files with 302 additions and 1 deletions
@@ -0,0 +1,95 @@
import type { GeneralTriggerState } from '../../../domain/entities.js';
import type { Constraint, ConstraintContext } from '../../../constraints/types.js';
import {
allowDiplomacyBetweenStatus,
beChief,
existsDestNation,
notBeNeutral,
occupiedCity,
suppliedCity,
} from '../../../constraints/presets.js';
import type { GeneralActionDefinition } from '../../definition.js';
import type {
GeneralActionOutcome,
GeneralActionResolveContext,
} from '../../engine.js';
import { createLogEffect } from '../../engine.js';
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
import type { TurnCommandEnv } from '../commandEnv.js';
import type { NationTurnCommandSpec } from './index.js';
export interface NonAggressionCancelProposalArgs {
destNationId: number;
}
const ACTION_NAME = '불가침 파기 제의';
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<
TriggerState extends GeneralTriggerState = GeneralTriggerState
> implements
GeneralActionDefinition<TriggerState, NonAggressionCancelProposalArgs>
{
public readonly key = 'che_불가침파기제의';
public readonly name = ACTION_NAME;
parseArgs(raw: unknown): NonAggressionCancelProposalArgs | null {
const data = raw as { destNationId?: unknown };
const destNationId = parseNationId(data?.destNationId);
if (destNationId === null) {
return null;
}
return { destNationId };
}
buildConstraints(
_ctx: ConstraintContext,
_args: NonAggressionCancelProposalArgs
): Constraint[] {
return [
beChief(),
notBeNeutral(),
occupiedCity(),
suppliedCity(),
existsDestNation(),
allowDiplomacyBetweenStatus(
[DIPLOMACY_NON_AGGRESSION],
'불가침 중인 상대국에게만 가능합니다.'
),
];
}
resolve(
_context: GeneralActionResolveContext<TriggerState>,
args: NonAggressionCancelProposalArgs
): GeneralActionOutcome<TriggerState> {
return {
effects: [
createLogEffect(
`${ACTION_NAME}을 준비했습니다. (국가 ${args.destNationId})`,
{
scope: LogScope.GENERAL,
category: LogCategory.ACTION,
format: LogFormat.MONTH,
}
),
],
};
}
}
export const commandSpec: NationTurnCommandSpec = {
key: 'che_불가침파기제의',
category: '외교',
reqArg: true,
args: { destNationId: 0 },
createDefinition: (_env: TurnCommandEnv) => new ActionDefinition(),
};
@@ -6,6 +6,7 @@ export const NATION_TURN_COMMAND_KEYS = [
'che_발령',
'che_선전포고',
'che_불가침제의',
'che_불가침파기제의',
'che_의병모집',
] as const;
@@ -29,6 +30,7 @@ const defaultImporters: Record<
che_발령: async () => import('./che_발령.js'),
che_선전포고: async () => import('./che_선전포고.js'),
che_불가침제의: async () => import('./che_불가침제의.js'),
che_불가침파기제의: async () => import('./che_불가침파기제의.js'),
che_의병모집: async () => import('./che_의병모집.js'),
};