feat: implement diplomacy system with state management and processing logic

This commit is contained in:
2026-01-02 17:48:44 +00:00
parent f137c5be65
commit cb86a47f94
13 changed files with 606 additions and 45 deletions
+11
View File
@@ -95,6 +95,7 @@ export interface TurnEngineDiplomacyRow {
destNationId: number;
stateCode: number;
term: number;
meta: JsonValue;
}
export interface TurnEngineTroopRow {
@@ -307,6 +308,12 @@ export interface TurnEngineDiplomacyCreateManyInput {
meta: InputJsonValue;
}
export interface TurnEngineDiplomacyUpdateInput {
stateCode: number;
term: number;
meta: InputJsonValue;
}
export interface TurnEngineEventCreateManyInput {
targetCode: string;
priority: number;
@@ -379,6 +386,10 @@ export interface TurnEngineDatabaseClient {
createMany(args: {
data: TurnEngineDiplomacyCreateManyInput[];
}): Promise<unknown>;
update(args: {
where: { srcNationId: number; destNationId: number };
data: TurnEngineDiplomacyUpdateInput;
}): Promise<unknown>;
deleteMany(args?: unknown): Promise<unknown>;
};
troop: {
+30 -1
View File
@@ -76,6 +76,19 @@ export interface NationPatchEffect {
targetId?: NationId;
}
export interface DiplomacyPatchEffect {
type: 'diplomacy:patch';
srcNationId: NationId;
destNationId: NationId;
patch: {
state?: number;
term?: number;
dead?: number;
deadDelta?: number;
meta?: Record<string, unknown>;
};
}
export interface LogEffect {
type: 'log';
entry: LogEntryDraft;
@@ -93,6 +106,7 @@ export type GeneralActionEffect<
| GeneralAddEffect<TriggerState>
| CityPatchEffect
| NationPatchEffect
| DiplomacyPatchEffect
| LogEffect
| NextTurnOverrideEffect;
@@ -176,6 +190,17 @@ export const createNationPatchEffect = (
...(targetId !== undefined ? { targetId } : {}),
});
export const createDiplomacyPatchEffect = (
srcNationId: NationId,
destNationId: NationId,
patch: DiplomacyPatchEffect['patch']
): DiplomacyPatchEffect => ({
type: 'diplomacy:patch',
srcNationId,
destNationId,
patch,
});
export const createLogEffect = (
message: string,
options: Partial<Omit<LogEntryDraft, 'text'>> = {}
@@ -224,6 +249,7 @@ export const resolveGeneralAction = <
nations: [],
};
const pendingEffects: GeneralActionEffect[] = [];
const [nextWorld, worldPatches] = produceWithPatches(
{
general: context.general,
@@ -296,6 +322,9 @@ export const resolveGeneralAction = <
case 'general:add':
createdGenerals.push(effect.general as General);
break;
case 'diplomacy:patch':
pendingEffects.push(effect);
break;
case 'general:patch':
case 'city:patch':
case 'nation:patch':
@@ -359,7 +388,7 @@ export const resolveGeneralAction = <
nation: nextWorld.nation as Nation | null,
nextTurnAt,
logs,
effects: [], // 이제 effects는 직접 사용되지 않음 (이미 반영됨)
effects: pendingEffects,
};
if (nextWorld.city) {
resolution.city = nextWorld.city as City;
@@ -2,6 +2,7 @@ import type { GeneralTriggerState } from '../../../domain/entities.js';
import type { Constraint, ConstraintContext } from '../../../constraints/types.js';
import {
beChief,
disallowDiplomacyBetweenStatus,
existsDestNation,
notBeNeutral,
occupiedCity,
@@ -12,7 +13,7 @@ import type {
GeneralActionOutcome,
GeneralActionResolveContext,
} from '../../engine.js';
import { createLogEffect } from '../../engine.js';
import { createDiplomacyPatchEffect, createLogEffect } from '../../engine.js';
import { LogCategory, LogFormat, LogScope } from '../../../logging/types.js';
import type { TurnCommandEnv } from '../commandEnv.js';
import type { NationTurnCommandSpec } from './index.js';
@@ -22,6 +23,9 @@ export interface DeclareWarArgs {
}
const ACTION_NAME = '선전포고';
// legacy 규칙: 선전포고 상태는 24턴 유지.
const DIPLOMACY_DECLARE = 1;
const DECLARE_TERM = 24;
const parseNationId = (raw: unknown): number | null => {
if (typeof raw !== 'number' || !Number.isFinite(raw)) {
@@ -52,18 +56,45 @@ export class ActionDefinition<
suppliedCity(),
beChief(),
existsDestNation(),
disallowDiplomacyBetweenStatus({
0: '아국과 이미 교전중입니다.',
1: '아국과 이미 선포중입니다.',
7: '불가침국입니다.',
}),
];
}
resolve(
_context: GeneralActionResolveContext<TriggerState>,
context: GeneralActionResolveContext<TriggerState>,
args: DeclareWarArgs
): GeneralActionOutcome<TriggerState> {
void _context;
const nationId = context.nation?.id;
if (nationId === undefined || nationId <= 0) {
return {
effects: [
createLogEffect(
`${ACTION_NAME}을 준비했지만 국가 정보가 없습니다.`,
{
scope: LogScope.GENERAL,
category: LogCategory.ACTION,
format: LogFormat.MONTH,
}
),
],
};
}
return {
effects: [
createDiplomacyPatchEffect(nationId, args.destNationId, {
state: DIPLOMACY_DECLARE,
term: DECLARE_TERM,
}),
createDiplomacyPatchEffect(args.destNationId, nationId, {
state: DIPLOMACY_DECLARE,
term: DECLARE_TERM,
}),
createLogEffect(
`${ACTION_NAME}준비했습니다. (국가 ${args.destNationId})`,
`${ACTION_NAME}실행했습니다. (국가 ${args.destNationId})`,
{
scope: LogScope.GENERAL,
category: LogCategory.ACTION,