import { JosaUtil } from '@sammo-ts/common'; import { createDiplomacyPatchEffect, createLogEffect, createNationPatchEffect, type GeneralActionEffect, } from '../actions/engine.js'; import { allow, unknownOrDeny } from '../constraints/helpers.js'; import type { Constraint } from '../constraints/types.js'; import type { GeneralTriggerState, Nation, TriggerValue } from '../domain/entities.js'; import { LogCategory, LogFormat, LogScope } from '../logging/types.js'; import { DIPLOMACY_STATE } from './constants.js'; export type InstantDiplomacyResponseAction = 'noAggression' | 'cancelNA' | 'stopWar'; export interface InstantDiplomacyResponseContext { actor: { id: number; name: string; nationId: number; }; actorNation: Nation; proposer: { id: number; name: string; nationId: number; }; proposerNation: Nation; currentYear: number; currentMonth: number; } export interface InstantDiplomacyResponseArgs { action: InstantDiplomacyResponseAction; treatyYear?: number; treatyMonth?: number; } export const destGeneralBelongsToDestNation = (): Constraint => ({ name: 'destGeneralBelongsToDestNation', requires: (ctx) => { const requirements = []; if (ctx.destGeneralId !== undefined) { requirements.push({ kind: 'destGeneral', id: ctx.destGeneralId } as const); } if (ctx.destNationId !== undefined) { requirements.push({ kind: 'destNation', id: ctx.destNationId } as const); } return requirements; }, test: (ctx, view) => { if (ctx.destGeneralId === undefined || ctx.destNationId === undefined) { return unknownOrDeny(ctx, [], '제의 장수가 국가 소속이 아닙니다'); } const generalRequirement = { kind: 'destGeneral', id: ctx.destGeneralId } as const; const nationRequirement = { kind: 'destNation', id: ctx.destNationId } as const; if (!view.has(generalRequirement) || !view.has(nationRequirement)) { return unknownOrDeny( ctx, [generalRequirement, nationRequirement].filter((requirement) => !view.has(requirement)), '제의 장수가 국가 소속이 아닙니다' ); } const destGeneral = view.get(generalRequirement) as { nationId?: unknown } | null; return destGeneral?.nationId === ctx.destNationId ? allow() : { kind: 'deny', reason: '제의 장수가 국가 소속이 아닙니다' }; }, }); export interface InstantDiplomacyResponseResolution { actionKey: 'che_불가침수락' | 'che_불가침파기수락' | 'che_종전수락'; diplomacyDetail: string; effects: GeneralActionEffect[]; refreshFront: boolean; } const resolveMonthIndex = (year: number, month: number): number => year * 12 + month - 1; const buildNonAggressionEffects = ( context: InstantDiplomacyResponseContext, treatyYear: number, treatyMonth: number ): GeneralActionEffect[] => { const actorNation = context.actorNation; const proposerNation = context.proposerNation; const actorNationName = actorNation.name; const proposerNationName = proposerNation.name; const actorJosaWa = JosaUtil.pick(actorNationName, '와'); const proposerJosaWa = JosaUtil.pick(proposerNationName, '와'); const proposerMeta = { ...proposerNation.meta }; const recvAssist = proposerMeta.recv_assist && typeof proposerMeta.recv_assist === 'object' && !Array.isArray(proposerMeta.recv_assist) ? (proposerMeta.recv_assist as Record) : {}; const respAssist = proposerMeta.resp_assist && typeof proposerMeta.resp_assist === 'object' && !Array.isArray(proposerMeta.resp_assist) ? { ...(proposerMeta.resp_assist as Record) } : ({} as Record); const assistKey = `n${actorNation.id}`; const recvEntry = recvAssist[assistKey]; const recvAmount = Array.isArray(recvEntry) && typeof recvEntry[1] === 'number' && Number.isFinite(recvEntry[1]) ? recvEntry[1] : 0; respAssist[assistKey] = [actorNation.id, recvAmount]; const currentMonth = resolveMonthIndex(context.currentYear, context.currentMonth); const targetMonth = treatyYear * 12 + treatyMonth; const term = targetMonth - currentMonth; return [ createDiplomacyPatchEffect(actorNation.id, proposerNation.id, { state: DIPLOMACY_STATE.NON_AGGRESSION, term, }), createDiplomacyPatchEffect(proposerNation.id, actorNation.id, { state: DIPLOMACY_STATE.NON_AGGRESSION, term, }), createNationPatchEffect( { meta: { ...proposerMeta, resp_assist: respAssist, }, }, proposerNation.id ), createLogEffect( `${proposerNationName}${proposerJosaWa} ${treatyYear}년 ${treatyMonth}월까지 불가침 성공`, { scope: LogScope.GENERAL, category: LogCategory.HISTORY, generalId: context.actor.id, format: LogFormat.YEAR_MONTH, } ), createLogEffect( `${proposerNationName}${proposerJosaWa} ${treatyYear}년 ${treatyMonth}월까지 불가침에 성공했습니다.`, { scope: LogScope.GENERAL, category: LogCategory.ACTION, generalId: context.actor.id, format: LogFormat.PLAIN, } ), createLogEffect( `${actorNationName}${actorJosaWa} ${treatyYear}년 ${treatyMonth}월까지 불가침 성공`, { scope: LogScope.GENERAL, category: LogCategory.HISTORY, generalId: context.proposer.id, format: LogFormat.YEAR_MONTH, } ), createLogEffect( `${actorNationName}${actorJosaWa} ${treatyYear}년 ${treatyMonth}월까지 불가침에 성공했습니다.`, { scope: LogScope.GENERAL, category: LogCategory.ACTION, generalId: context.proposer.id, format: LogFormat.PLAIN, } ), ]; }; const buildCancelNonAggressionEffects = ( context: InstantDiplomacyResponseContext ): GeneralActionEffect[] => { const actorNation = context.actorNation; const proposerNation = context.proposerNation; const actorNationName = actorNation.name; const proposerNationName = proposerNation.name; const actorName = context.actor.name; const actorJosaYi = JosaUtil.pick(actorName, '이'); const actorNationJosaYi = JosaUtil.pick(actorNationName, '이'); const actorNationJosaWa = JosaUtil.pick(actorNationName, '와'); const proposerJosaWa = JosaUtil.pick(proposerNationName, '와'); return [ createDiplomacyPatchEffect(actorNation.id, proposerNation.id, { state: DIPLOMACY_STATE.TRADE, term: 0, }), createDiplomacyPatchEffect(proposerNation.id, actorNation.id, { state: DIPLOMACY_STATE.TRADE, term: 0, }), createLogEffect(`${proposerNationName}${proposerJosaWa}의 불가침 파기 수락`, { scope: LogScope.GENERAL, category: LogCategory.HISTORY, generalId: context.actor.id, format: LogFormat.YEAR_MONTH, }), createLogEffect(`${proposerNationName}${proposerJosaWa}의 불가침을 파기했습니다.`, { scope: LogScope.GENERAL, category: LogCategory.ACTION, generalId: context.actor.id, format: LogFormat.PLAIN, }), createLogEffect( `【파기】${actorNationName}${actorNationJosaYi} ${proposerNationName}${proposerJosaWa}의 불가침 조약을 파기 하였습니다.`, { scope: LogScope.SYSTEM, category: LogCategory.HISTORY, format: LogFormat.YEAR_MONTH, } ), createLogEffect( `${actorName}${actorJosaYi} ${proposerNationName}${proposerJosaWa}의 불가침 조약을 파기 하였습니다.`, { scope: LogScope.SYSTEM, category: LogCategory.SUMMARY, format: LogFormat.MONTH, } ), createLogEffect(`${actorNationName}${actorNationJosaWa}의 불가침 파기 성공`, { scope: LogScope.GENERAL, category: LogCategory.HISTORY, generalId: context.proposer.id, format: LogFormat.YEAR_MONTH, }), createLogEffect(`${actorNationName}${actorNationJosaWa}의 불가침 파기에 성공했습니다.`, { scope: LogScope.GENERAL, category: LogCategory.ACTION, generalId: context.proposer.id, format: LogFormat.PLAIN, }), ]; }; const buildStopWarEffects = ( context: InstantDiplomacyResponseContext ): GeneralActionEffect[] => { const actorNation = context.actorNation; const proposerNation = context.proposerNation; const actorNationName = actorNation.name; const proposerNationName = proposerNation.name; const actorName = context.actor.name; const actorJosaYi = JosaUtil.pick(actorName, '이'); const actorNationJosaYi = JosaUtil.pick(actorNationName, '이'); const actorNationJosaWa = JosaUtil.pick(actorNationName, '와'); const proposerJosaWa = JosaUtil.pick(proposerNationName, '와'); return [ createDiplomacyPatchEffect(actorNation.id, proposerNation.id, { state: DIPLOMACY_STATE.TRADE, term: 0, }), createDiplomacyPatchEffect(proposerNation.id, actorNation.id, { state: DIPLOMACY_STATE.TRADE, term: 0, }), createLogEffect(`${proposerNationName}${proposerJosaWa} 종전 수락`, { scope: LogScope.GENERAL, category: LogCategory.HISTORY, generalId: context.actor.id, format: LogFormat.YEAR_MONTH, }), createLogEffect(`${proposerNationName}${proposerJosaWa} 종전에 합의했습니다.`, { scope: LogScope.GENERAL, category: LogCategory.ACTION, generalId: context.actor.id, format: LogFormat.PLAIN, }), createLogEffect(`${proposerNationName}${proposerJosaWa} 종전`, { scope: LogScope.NATION, nationId: actorNation.id, category: LogCategory.HISTORY, format: LogFormat.YEAR_MONTH, }), createLogEffect( `【종전】${actorNationName}${actorNationJosaYi} ${proposerNationName}${proposerJosaWa} 종전 합의 하였습니다.`, { scope: LogScope.SYSTEM, category: LogCategory.HISTORY, format: LogFormat.YEAR_MONTH, } ), createLogEffect( `${actorName}${actorJosaYi} ${proposerNationName}${proposerJosaWa} 종전 합의 하였습니다.`, { scope: LogScope.SYSTEM, category: LogCategory.SUMMARY, format: LogFormat.MONTH, } ), createLogEffect(`${actorNationName}${actorNationJosaWa} 종전 성공`, { scope: LogScope.GENERAL, generalId: context.proposer.id, category: LogCategory.HISTORY, format: LogFormat.YEAR_MONTH, }), createLogEffect(`${actorNationName}${actorNationJosaWa} 종전에 성공했습니다.`, { scope: LogScope.GENERAL, generalId: context.proposer.id, category: LogCategory.ACTION, format: LogFormat.PLAIN, }), createLogEffect(`${actorNationName}${actorNationJosaWa} 종전`, { scope: LogScope.NATION, nationId: proposerNation.id, category: LogCategory.HISTORY, format: LogFormat.YEAR_MONTH, }), ]; }; export const resolveInstantDiplomacyResponse = ( context: InstantDiplomacyResponseContext, args: InstantDiplomacyResponseArgs ): InstantDiplomacyResponseResolution => { if (args.action === 'noAggression') { if ( typeof args.treatyYear !== 'number' || !Number.isInteger(args.treatyYear) || typeof args.treatyMonth !== 'number' || !Number.isInteger(args.treatyMonth) || args.treatyMonth < 1 || args.treatyMonth > 12 ) { throw new Error('Invalid non-aggression treaty term.'); } return { actionKey: 'che_불가침수락', diplomacyDetail: `${args.treatyYear}년 ${args.treatyMonth}월까지 불가침 합의`, effects: buildNonAggressionEffects(context, args.treatyYear, args.treatyMonth), refreshFront: false, }; } if (args.action === 'cancelNA') { return { actionKey: 'che_불가침파기수락', diplomacyDetail: `${context.proposerNation.name}국과 불가침 파기 합의`, effects: buildCancelNonAggressionEffects(context), refreshFront: false, }; } return { actionKey: 'che_종전수락', diplomacyDetail: `${context.proposerNation.name}국과 종전 합의`, effects: buildStopWarEffects(context), refreshFront: true, }; };