import { clamp } from 'es-toolkit'; import { DIPLOMACY_STATE } from './constants.js'; export { DIPLOMACY_STATE } from './constants.js'; export const DEFAULT_DECLARE_WAR_TERM = 24; export const DEFAULT_WAR_TERM = 6; const MAX_WAR_TERM = 13; export interface DiplomacyEntry { fromNationId: number; toNationId: number; state: number; term: number; dead: number; meta: Record; } export interface DiplomacyPatch { state?: number; term?: number; dead?: number; deadDelta?: number; meta?: Record; } export const buildDiplomacyKey = (srcNationId: number, destNationId: number): string => `${srcNationId}:${destNationId}`; export const buildDefaultDiplomacy = (srcNationId: number, destNationId: number): DiplomacyEntry => ({ fromNationId: srcNationId, toNationId: destNationId, state: DIPLOMACY_STATE.TRADE, term: 0, dead: 0, meta: {}, }); export const applyDiplomacyPatch = (entry: DiplomacyEntry, patch: DiplomacyPatch): DiplomacyEntry => { const nextDead = typeof patch.dead === 'number' ? patch.dead : typeof patch.deadDelta === 'number' ? entry.dead + patch.deadDelta : entry.dead; return { ...entry, state: patch.state ?? entry.state, term: patch.term ?? entry.term, dead: clamp(nextDead, 0, Number.MAX_SAFE_INTEGER), meta: patch.meta ? { ...entry.meta, ...patch.meta } : entry.meta, }; }; export const readDiplomacyMeta = (meta: Record): { meta: Record; dead: number } => { const rawDead = meta.dead; const dead = typeof rawDead === 'number' ? rawDead : 0; const cleaned = { ...meta }; delete cleaned.dead; return { meta: cleaned, dead }; }; export const buildDiplomacyMeta = (entry: DiplomacyEntry): Record => ({ ...entry.meta, dead: entry.dead, }); export const processDiplomacyMonth = ( diplomacy: DiplomacyEntry[], generalCounts: Map ): DiplomacyEntry[] => { const next = diplomacy.map((entry) => ({ ...entry, meta: { ...entry.meta }, })); const byKey = new Map( next.map((entry) => [buildDiplomacyKey(entry.fromNationId, entry.toNationId), entry]) ); // 전쟁 기간 갱신: 사상자에 따라 term 증가, 잔여 사상자 유지. for (const entry of next) { if (entry.state !== DIPLOMACY_STATE.WAR || entry.dead <= 0) { continue; } const genCount = Math.max(1, generalCounts.get(entry.fromNationId) ?? 1); const termIncrease = Math.floor(entry.dead / 100 / genCount); if (termIncrease <= 0) { continue; } entry.dead -= termIncrease * 100 * genCount; entry.term = clamp(entry.term + termIncrease, 0, MAX_WAR_TERM); } // 전쟁 종료 판정: 양방 term이 1 이하이면 통상으로 전환. const processedPairs = new Set(); for (const entry of next) { if (entry.state !== DIPLOMACY_STATE.WAR || entry.term > 1) { continue; } const pairKey = buildDiplomacyKey( Math.min(entry.fromNationId, entry.toNationId), Math.max(entry.fromNationId, entry.toNationId) ); if (processedPairs.has(pairKey)) { continue; } const opposite = byKey.get(buildDiplomacyKey(entry.toNationId, entry.fromNationId)); if (opposite && opposite.state === DIPLOMACY_STATE.WAR && opposite.term <= 1) { entry.state = DIPLOMACY_STATE.TRADE; entry.term = 0; opposite.state = DIPLOMACY_STATE.TRADE; opposite.term = 0; processedPairs.add(pairKey); } } // 사상자 초기화 및 term 감소. for (const entry of next) { if (entry.state !== DIPLOMACY_STATE.WAR) { entry.dead = 0; } entry.term = Math.max(0, entry.term - 1); } // 불가침/선전포고 만료 처리. for (const entry of next) { if (entry.state === DIPLOMACY_STATE.NON_AGGRESSION && entry.term === 0) { entry.state = DIPLOMACY_STATE.TRADE; } else if (entry.state === DIPLOMACY_STATE.DECLARATION && entry.term === 0) { entry.state = DIPLOMACY_STATE.WAR; entry.term = DEFAULT_WAR_TERM; } } return next; }; export * from './frontState.js'; export * from './instantResponse.js';