From 6497b09b9d7b2fa388a53d5fe4c24c91973333c6 Mon Sep 17 00:00:00 2001 From: Hide_D Date: Sun, 11 Jan 2026 03:26:14 +0000 Subject: [PATCH] =?UTF-8?q?=EC=82=AC=EB=A0=B9=ED=84=B4=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../logic/src/actions/turn/nation/che_감축.ts | 107 ++++++++------ .../src/actions/turn/nation/che_국기변경.ts | 115 ++++++++++----- .../src/actions/turn/nation/che_국호변경.ts | 52 ++++--- .../logic/src/actions/turn/nation/che_몰수.ts | 37 +++-- .../actions/turn/nation/che_무작위수도이전.ts | 139 +++++++++++------- .../src/actions/turn/nation/che_물자원조.ts | 95 +++++++----- .../logic/src/actions/turn/nation/che_증축.ts | 89 ++++++----- .../logic/src/actions/turn/nation/che_천도.ts | 85 ++++++----- .../logic/src/actions/turn/nation/index.ts | 2 +- .../logic/test/actions/turn/nation.test.ts | 90 ++++++++++++ 10 files changed, 530 insertions(+), 281 deletions(-) diff --git a/packages/logic/src/actions/turn/nation/che_감축.ts b/packages/logic/src/actions/turn/nation/che_감축.ts index 99ef94c..a9b68be 100644 --- a/packages/logic/src/actions/turn/nation/che_감축.ts +++ b/packages/logic/src/actions/turn/nation/che_감축.ts @@ -1,12 +1,12 @@ import type { City, GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/entities.js'; import type { Constraint, ConstraintContext, StateView } from '@sammo-ts/logic/constraints/types.js'; -import { - beChief, - occupiedCity, - suppliedCity, -} from '@sammo-ts/logic/constraints/presets.js'; +import { beChief, occupiedCity, suppliedCity } from '@sammo-ts/logic/constraints/presets.js'; import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js'; -import type { GeneralActionEffect, GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; +import type { + GeneralActionEffect, + GeneralActionOutcome, + GeneralActionResolveContext, +} from '@sammo-ts/logic/actions/engine.js'; import { createLogEffect, createNationPatchEffect, createCityPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; @@ -14,7 +14,7 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js' import type { NationTurnCommandSpec } from './index.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; -export interface ReduceCityArgs { } +export interface ReduceCityArgs {} export interface ReduceCityResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -37,7 +37,7 @@ export class ActionDefinition< public readonly key = 'che_감축'; public readonly name = ACTION_NAME; - constructor(private readonly env: TurnCommandEnv) { } + constructor(private readonly env: TurnCommandEnv) {} parseArgs(_raw: unknown): ReduceCityArgs | null { return {}; @@ -56,7 +56,7 @@ export class ActionDefinition< name: 'reducibleCity', requires: (ctx) => [ { kind: 'nation', id: ctx.nationId! }, - { kind: 'city', id: 0 } + { kind: 'city', id: 0 }, ], test: (ctx: ConstraintContext, view: StateView) => { const nation = view.get({ kind: 'nation', id: ctx.nationId! }) as Nation | undefined; @@ -69,8 +69,8 @@ export class ActionDefinition< if (capitalCity.level <= 4) return { kind: 'deny', reason: '더이상 감축할 수 없습니다.' }; return { kind: 'allow' }; - } - } + }, + }, ]; } @@ -93,44 +93,59 @@ export class ActionDefinition< const josaYiNation = JosaUtil.pick(nationName, '이'); const effects: Array> = [ - createCityPatchEffect({ - level: capitalCity.level - 1, - population: Math.max(capitalCity.population - POP_INCREASE, MIN_POP), - agriculture: Math.max(capitalCity.agriculture - DEVEL_INCREASE, 0), - commerce: Math.max(capitalCity.commerce - DEVEL_INCREASE, 0), - security: Math.max(capitalCity.security - DEVEL_INCREASE, 0), - defence: Math.max(capitalCity.defence - WALL_INCREASE, 0), - wall: Math.max(capitalCity.wall - WALL_INCREASE, 0), - populationMax: capitalCity.populationMax - POP_INCREASE, - agricultureMax: capitalCity.agricultureMax - DEVEL_INCREASE, - commerceMax: capitalCity.commerceMax - DEVEL_INCREASE, - securityMax: capitalCity.securityMax - DEVEL_INCREASE, - defenceMax: capitalCity.defenceMax - WALL_INCREASE, - wallMax: capitalCity.wallMax - WALL_INCREASE, - }, capitalCity.id), - createNationPatchEffect({ - gold: nation.gold + recoverAmount, - rice: nation.rice + recoverAmount, - }, nation.id), + createCityPatchEffect( + { + level: capitalCity.level - 1, + population: Math.max(capitalCity.population - POP_INCREASE, MIN_POP), + agriculture: Math.max(capitalCity.agriculture - DEVEL_INCREASE, 0), + commerce: Math.max(capitalCity.commerce - DEVEL_INCREASE, 0), + security: Math.max(capitalCity.security - DEVEL_INCREASE, 0), + defence: Math.max(capitalCity.defence - WALL_INCREASE, 0), + wall: Math.max(capitalCity.wall - WALL_INCREASE, 0), + populationMax: capitalCity.populationMax - POP_INCREASE, + agricultureMax: capitalCity.agricultureMax - DEVEL_INCREASE, + commerceMax: capitalCity.commerceMax - DEVEL_INCREASE, + securityMax: capitalCity.securityMax - DEVEL_INCREASE, + defenceMax: capitalCity.defenceMax - WALL_INCREASE, + wallMax: capitalCity.wallMax - WALL_INCREASE, + }, + capitalCity.id + ), + createNationPatchEffect( + { + gold: nation.gold + recoverAmount, + rice: nation.rice + recoverAmount, + }, + nation.id + ), // Global Action Log - createLogEffect(`${generalName}${josaYi} ${destCityName}${josaUl} ${ACTION_NAME}하였습니다.`, { - scope: LogScope.SYSTEM, - category: LogCategory.ACTION, - format: LogFormat.PLAIN, - }), + createLogEffect( + `${generalName}${josaYi} ${destCityName}${josaUl} ${ACTION_NAME}하였습니다.`, + { + scope: LogScope.SYSTEM, + category: LogCategory.ACTION, + format: LogFormat.PLAIN, + } + ), // Global History Log - createLogEffect(`【${ACTION_NAME}】${nationName}${josaYiNation} ${destCityName}${josaUl} ${ACTION_NAME}하였습니다.`, { - scope: LogScope.SYSTEM, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }), + createLogEffect( + `【${ACTION_NAME}】${nationName}${josaYiNation} ${destCityName}${josaUl} ${ACTION_NAME}하였습니다.`, + { + scope: LogScope.SYSTEM, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + } + ), // Actor Nation History Log - createLogEffect(`${generalName}${josaYi} ${destCityName}${josaUl} ${ACTION_NAME}`, { - scope: LogScope.NATION, - nationId: nation.id, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }), + createLogEffect( + `${generalName}${josaYi} ${destCityName}${josaUl} ${ACTION_NAME}`, + { + scope: LogScope.NATION, + nationId: nation.id, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + } + ), // General Action Log createLogEffect(`${destCityName}${josaUl} ${ACTION_NAME}했습니다.`, { scope: LogScope.GENERAL, diff --git a/packages/logic/src/actions/turn/nation/che_국기변경.ts b/packages/logic/src/actions/turn/nation/che_국기변경.ts index cab9bcf..fa56e05 100644 --- a/packages/logic/src/actions/turn/nation/che_국기변경.ts +++ b/packages/logic/src/actions/turn/nation/che_국기변경.ts @@ -1,13 +1,12 @@ import type { GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/entities.js'; -import type { Constraint, ConstraintContext } from '@sammo-ts/logic/constraints/types.js'; -import type { StateView } from '@sammo-ts/logic/world/stateView.js'; -import { - beChief, - occupiedCity, - suppliedCity, -} from '@sammo-ts/logic/constraints/presets.js'; +import type { Constraint, ConstraintContext, StateView } from '@sammo-ts/logic/constraints/types.js'; +import { beChief, occupiedCity, suppliedCity } from '@sammo-ts/logic/constraints/presets.js'; import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js'; -import type { GeneralActionEffect, GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; +import type { + GeneralActionEffect, + GeneralActionOutcome, + GeneralActionResolveContext, +} from '@sammo-ts/logic/actions/engine.js'; import { createLogEffect, createNationPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; @@ -21,10 +20,39 @@ export interface ChangeFlagArgs { const ACTION_NAME = '국기변경'; const NATION_COLORS = [ - '#FF0000', '#800000', '#A0522D', '#FF6347', '#FFA500', '#FFDAB9', '#FFD700', '#FFFF00', '#7CFC00', '#00FF00', - '#808000', '#008000', '#2E8B57', '#008080', '#20B2AA', '#6495ED', '#7FFFD4', '#AFEEEE', '#87CEEB', '#00FFFF', - '#00BFFF', '#0000FF', '#000080', '#483D8B', '#7B68EE', '#BA55D3', '#800080', '#FF00FF', '#FFC0CB', '#F5F5DC', - '#E0FFFF', '#FFFFFF', '#A9A9A9', + '#FF0000', + '#800000', + '#A0522D', + '#FF6347', + '#FFA500', + '#FFDAB9', + '#FFD700', + '#FFFF00', + '#7CFC00', + '#00FF00', + '#808000', + '#008000', + '#2E8B57', + '#008080', + '#20B2AA', + '#6495ED', + '#7FFFD4', + '#AFEEEE', + '#87CEEB', + '#00FFFF', + '#00BFFF', + '#0000FF', + '#000080', + '#483D8B', + '#7B68EE', + '#BA55D3', + '#800080', + '#FF00FF', + '#FFC0CB', + '#F5F5DC', + '#E0FFFF', + '#FFFFFF', + '#A9A9A9', ]; export class ActionDefinition< @@ -50,11 +78,12 @@ export class ActionDefinition< requires: (ctx) => [{ kind: 'nation', id: ctx.nationId! }], test: (ctx: ConstraintContext, view: StateView) => { const nation = view.get({ kind: 'nation', id: ctx.nationId! }) as Nation | undefined; - const canChange = ((nation?.meta as any)?.[`can_${ACTION_NAME}`] ?? 0) !== 0; + const canChangeRaw = nation?.meta[`can_${ACTION_NAME}`]; + const canChange = (typeof canChangeRaw === 'number' ? canChangeRaw : 0) !== 0; if (!canChange) return { kind: 'deny', reason: '더이상 변경이 불가능합니다.' }; return { kind: 'allow' }; - } - } + }, + }, ]; } @@ -75,32 +104,44 @@ export class ActionDefinition< const josaYiNation = JosaUtil.pick(nationName, '이'); const effects: Array> = [ - createNationPatchEffect({ - color: color!, - meta: { - ...nation.meta, - [`can_${ACTION_NAME}`]: 0, + createNationPatchEffect( + { + color: color!, + meta: { + ...nation.meta, + [`can_${ACTION_NAME}`]: 0, + }, }, - }, nation.id), + nation.id + ), // Global Action Log - createLogEffect(`${generalName}${josaYi} 국기를 변경하였습니다.`, { - scope: LogScope.SYSTEM, - category: LogCategory.ACTION, - format: LogFormat.PLAIN, - }), + createLogEffect( + `${generalName}${josaYi} 국기를 변경하였습니다.`, + { + scope: LogScope.SYSTEM, + category: LogCategory.ACTION, + format: LogFormat.PLAIN, + } + ), // Global History Log - createLogEffect(`【${ACTION_NAME}】${nationName}${josaYiNation} 국기를 변경하였습니다.`, { - scope: LogScope.SYSTEM, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }), + createLogEffect( + `【${ACTION_NAME}】${nationName}${josaYiNation} 국기를 변경하였습니다.`, + { + scope: LogScope.SYSTEM, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + } + ), // Actor Nation History Log - createLogEffect(`${generalName}${josaYi} 국기를 변경하였습니다.`, { - scope: LogScope.NATION, - nationId: nation.id, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }), + createLogEffect( + `${generalName}${josaYi} 국기를 변경하였습니다.`, + { + scope: LogScope.NATION, + nationId: nation.id, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + } + ), // General Action Log createLogEffect(`국기를 변경하였습니다.`, { scope: LogScope.GENERAL, diff --git a/packages/logic/src/actions/turn/nation/che_국호변경.ts b/packages/logic/src/actions/turn/nation/che_국호변경.ts index 3bd6bb4..ac55bb5 100644 --- a/packages/logic/src/actions/turn/nation/che_국호변경.ts +++ b/packages/logic/src/actions/turn/nation/che_국호변경.ts @@ -1,13 +1,12 @@ import type { GeneralTriggerState, Nation } from '@sammo-ts/logic/domain/entities.js'; -import type { Constraint, ConstraintContext } from '@sammo-ts/logic/constraints/types.js'; -import { - beChief, - occupiedCity, - suppliedCity, - checkNationNameDuplicate, -} from '@sammo-ts/logic/constraints/presets.js'; +import type { Constraint, ConstraintContext, StateView } from '@sammo-ts/logic/constraints/types.js'; +import { beChief, occupiedCity, suppliedCity, checkNationNameDuplicate } from '@sammo-ts/logic/constraints/presets.js'; import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js'; -import type { GeneralActionEffect, GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; +import type { + GeneralActionEffect, + GeneralActionOutcome, + GeneralActionResolveContext, +} from '@sammo-ts/logic/actions/engine.js'; import { createLogEffect, createNationPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; @@ -46,12 +45,13 @@ export class ActionDefinition< name: 'canChangeNationName', requires: (ctx) => [{ kind: 'nation', id: ctx.nationId! }], test: (_ctx: ConstraintContext, view: StateView) => { - const nation = view.get({ kind: 'nation', id: ctx.nationId! }) as Nation | undefined; - const canChange = ((nation?.meta as any)?.[`can_${ACTION_NAME}`] ?? 0) !== 0; + const nation = view.get({ kind: 'nation', id: _ctx.nationId! }) as Nation | undefined; + const canChangeRaw = nation?.meta[`can_${ACTION_NAME}`]; + const canChange = (typeof canChangeRaw === 'number' ? canChangeRaw : 0) !== 0; if (!canChange) return { kind: 'deny', reason: '더이상 변경이 불가능합니다.' }; return { kind: 'allow' }; - } - } + }, + }, ]; } @@ -72,13 +72,16 @@ export class ActionDefinition< const josaYiNation = JosaUtil.pick(newNationName, '이'); const effects: Array> = [ - createNationPatchEffect({ - name: newNationName, - meta: { - ...nation.meta, - [`can_${ACTION_NAME}`]: 0, + createNationPatchEffect( + { + name: newNationName, + meta: { + ...nation.meta, + [`can_${ACTION_NAME}`]: 0, + }, }, - }, nation.id), + nation.id + ), // Global Action Log createLogEffect(`${generalName}${josaYi} 국호를 ${newNationName}로 변경하였습니다.`, { scope: LogScope.SYSTEM, @@ -86,11 +89,14 @@ export class ActionDefinition< format: LogFormat.PLAIN, }), // Global History Log - createLogEffect(`【${ACTION_NAME}】${oldNationName}${josaYiNation} 국호를 ${newNationName}로 변경하였습니다.`, { - scope: LogScope.SYSTEM, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }), + createLogEffect( + `【${ACTION_NAME}】${oldNationName}${josaYiNation} 국호를 ${newNationName}로 변경하였습니다.`, + { + scope: LogScope.SYSTEM, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + } + ), // Actor Nation History Log createLogEffect(`${generalName}${josaYi} 국호를 ${newNationName}로 변경하였습니다.`, { scope: LogScope.NATION, diff --git a/packages/logic/src/actions/turn/nation/che_몰수.ts b/packages/logic/src/actions/turn/nation/che_몰수.ts index ed5f45e..cc3e0a0 100644 --- a/packages/logic/src/actions/turn/nation/che_몰수.ts +++ b/packages/logic/src/actions/turn/nation/che_몰수.ts @@ -8,7 +8,11 @@ import { suppliedCity, } from '@sammo-ts/logic/constraints/presets.js'; import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js'; -import type { GeneralActionEffect, GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; +import type { + GeneralActionEffect, + GeneralActionOutcome, + GeneralActionResolveContext, +} from '@sammo-ts/logic/actions/engine.js'; import { createLogEffect, createNationPatchEffect, createGeneralPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; @@ -37,7 +41,7 @@ export class ActionDefinition< public readonly key = 'che_몰수'; public readonly name = ACTION_NAME; - constructor(private readonly env: TurnCommandEnv) { } + constructor(private readonly env: TurnCommandEnv) {} parseArgs(raw: unknown): SeizureArgs | null { const data = raw as { isGold?: boolean; amount?: number; destGeneralID?: number }; @@ -70,15 +74,12 @@ export class ActionDefinition< return { kind: 'deny', reason: '본인입니다' }; } return { kind: 'allow' }; - } - } + }, + }, ]; } - resolve( - context: SeizureResolveContext, - args: SeizureArgs - ): GeneralActionOutcome { + resolve(context: SeizureResolveContext, args: SeizureArgs): GeneralActionOutcome { const { general, nation, destGeneral } = context; if (!nation) { return { effects: [createLogEffect('국가 정보가 없습니다.', { scope: LogScope.GENERAL })] }; @@ -87,7 +88,7 @@ export class ActionDefinition< const resKey = args.isGold ? 'gold' : 'rice'; const resName = args.isGold ? '금' : '쌀'; - const actualAmount = clamp(args.amount, 0, (destGeneral as any)[resKey] ?? 0); + const actualAmount = clamp(args.amount, 0, destGeneral[resKey] ?? 0); if (actualAmount <= 0) { return { @@ -105,12 +106,18 @@ export class ActionDefinition< const josaUl = JosaUtil.pick(amountText, '을'); const effects: Array> = [ - createGeneralPatchEffect({ - [resKey]: (destGeneral as any)[resKey] - actualAmount, - }, destGeneral.id), - createNationPatchEffect({ - [resKey]: (nation as any)[resKey] + actualAmount, - }, nation.id), + createGeneralPatchEffect( + { + [resKey]: destGeneral[resKey] - actualAmount, + }, + destGeneral.id + ), + createNationPatchEffect( + { + [resKey]: nation[resKey] + actualAmount, + }, + nation.id + ), // Actor General Action Log createLogEffect(`${destGeneral.name}에게서 ${resName} ${amountText}${josaUl} 몰수했습니다.`, { scope: LogScope.GENERAL, diff --git a/packages/logic/src/actions/turn/nation/che_무작위수도이전.ts b/packages/logic/src/actions/turn/nation/che_무작위수도이전.ts index 6f61331..5e9e98d 100644 --- a/packages/logic/src/actions/turn/nation/che_무작위수도이전.ts +++ b/packages/logic/src/actions/turn/nation/che_무작위수도이전.ts @@ -1,20 +1,25 @@ import type { GeneralTriggerState, Nation, City, General } from '@sammo-ts/logic/domain/entities.js'; import type { Constraint, ConstraintContext, StateView } from '@sammo-ts/logic/constraints/types.js'; -import { - beMonarch, - occupiedCity, - suppliedCity, -} from '@sammo-ts/logic/constraints/presets.js'; +import { beMonarch, occupiedCity, suppliedCity } from '@sammo-ts/logic/constraints/presets.js'; import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js'; -import type { GeneralActionEffect, GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; -import { createLogEffect, createNationPatchEffect, createCityPatchEffect, createGeneralPatchEffect } from '@sammo-ts/logic/actions/engine.js'; +import type { + GeneralActionEffect, + GeneralActionOutcome, + GeneralActionResolveContext, +} from '@sammo-ts/logic/actions/engine.js'; +import { + createLogEffect, + createNationPatchEffect, + createCityPatchEffect, + createGeneralPatchEffect, +} from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js'; import type { NationTurnCommandSpec } from './index.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; -export interface RandomMoveCapitalArgs { } +export interface RandomMoveCapitalArgs {} export interface RandomMoveCapitalResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -27,7 +32,11 @@ const ACTION_NAME = '무작위 수도 이전'; export class ActionDefinition< TriggerState extends GeneralTriggerState = GeneralTriggerState, -> implements GeneralActionDefinition> { +> implements GeneralActionDefinition< + TriggerState, + RandomMoveCapitalArgs, + RandomMoveCapitalResolveContext +> { public readonly key = 'che_무작위수도이전'; public readonly name = ACTION_NAME; @@ -45,11 +54,12 @@ export class ActionDefinition< requires: (ctx) => [{ kind: 'nation', id: ctx.nationId! }], test: (ctx: ConstraintContext, view: StateView) => { const nation = view.get({ kind: 'nation', id: ctx.nationId! }) as Nation | undefined; - const canMove = ((nation?.meta as any)?.[`can_무작위수도이전`] ?? 0) > 0; + const canMoveRaw = nation?.meta[`can_무작위수도이전`]; + const canMove = (typeof canMoveRaw === 'number' ? canMoveRaw : 0) > 0; if (!canMove) return { kind: 'deny', reason: '더이상 변경이 불가능합니다.' }; return { kind: 'allow' }; - } - } + }, + }, ]; } @@ -85,41 +95,59 @@ export class ActionDefinition< const josaYiNation = JosaUtil.pick(nationName, '이'); const effects: Array> = [ - createNationPatchEffect({ - capitalCityId: destCity.id, - meta: { - ...nation.meta, - can_무작위수도이전: ((nation.meta as any).can_무작위수도이전 ?? 0) - 1, + createNationPatchEffect( + { + capitalCityId: destCity.id, + meta: { + ...nation.meta, + can_무작위수도이전: Number(nation.meta.can_무작위수도이전 ?? 0) - 1, + }, }, - }, nation.id), + nation.id + ), // Set new capital - createCityPatchEffect({ - nationId: nation.id, - }, destCity.id), + createCityPatchEffect( + { + nationId: nation.id, + }, + destCity.id + ), // Set old capital to neutral - createCityPatchEffect({ - nationId: 0, - frontState: 0, - }, oldCityId!), + createCityPatchEffect( + { + nationId: 0, + frontState: 0, + }, + oldCityId! + ), // Global Action Log - createLogEffect(`${generalName}${josaYi} ${destCityName}${josaRo} 수도 이전${generalName}${josaYi} ${destCityName}${josaRo} 수도 이전【${ACTION_NAME}】${nationName}${josaYiNation} ${destCityName}${josaRo} 수도 이전【${ACTION_NAME}】${nationName}${josaYiNation} ${destCityName}${josaRo} 수도 이전${generalName}${josaYi} ${destCityName}${josaRo} ${ACTION_NAME}${generalName}${josaYi} ${destCityName}${josaRo} ${ACTION_NAME}${destCityName}${josaRo} 국가를 옮겼습니다.`, { scope: LogScope.GENERAL, @@ -130,17 +158,24 @@ export class ActionDefinition< // Move all generals to new capital for (const targetGeneral of nationGenerals) { - effects.push(createGeneralPatchEffect({ - cityId: destCity.id, - }, targetGeneral.id)); + effects.push( + createGeneralPatchEffect( + { + cityId: destCity.id, + }, + targetGeneral.id + ) + ); if (targetGeneral.id !== general.id) { - effects.push(createLogEffect(`국가 수도를 ${destCityName}${josaRo} 옮겼습니다.`, { - scope: LogScope.GENERAL, - category: LogCategory.ACTION, - generalId: targetGeneral.id, - format: LogFormat.PLAIN, - })); + effects.push( + createLogEffect(`국가 수도를 ${destCityName}${josaRo} 옮겼습니다.`, { + scope: LogScope.GENERAL, + category: LogCategory.ACTION, + generalId: targetGeneral.id, + format: LogFormat.PLAIN, + }) + ); } } @@ -156,8 +191,8 @@ export const actionContextBuilder: ActionContextBuilder = (base, options) => { if (!worldRef) return null; const allCities = worldRef.listCities(); - const neutralCandidateCities = allCities.filter(c => c.nationId === 0 && c.level >= 5 && c.level <= 6); - const nationGenerals = worldRef.listGenerals().filter(g => g.nationId === base.general.nationId); + const neutralCandidateCities = allCities.filter((c) => c.nationId === 0 && c.level >= 5 && c.level <= 6); + const nationGenerals = worldRef.listGenerals().filter((g) => g.nationId === base.general.nationId); return { ...base, diff --git a/packages/logic/src/actions/turn/nation/che_물자원조.ts b/packages/logic/src/actions/turn/nation/che_물자원조.ts index 39d9e1d..cd46a8e 100644 --- a/packages/logic/src/actions/turn/nation/che_물자원조.ts +++ b/packages/logic/src/actions/turn/nation/che_물자원조.ts @@ -8,7 +8,11 @@ import { existsDestNation, } from '@sammo-ts/logic/constraints/presets.js'; import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js'; -import type { GeneralActionEffect, GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; +import type { + GeneralActionEffect, + GeneralActionOutcome, + GeneralActionResolveContext, +} from '@sammo-ts/logic/actions/engine.js'; import { createLogEffect, createNationPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; @@ -38,7 +42,7 @@ export class ActionDefinition< public readonly key = 'che_물자원조'; public readonly name = ACTION_NAME; - constructor(private readonly env: TurnCommandEnv) { } + constructor(private readonly env: TurnCommandEnv) {} parseArgs(raw: unknown): MaterialAidArgs | null { const data = raw as { destNationId?: number; amountList?: [number, number] }; @@ -70,28 +74,30 @@ export class ActionDefinition< return { kind: 'deny', reason: '작위 제한량 이상은 보낼 수 없습니다.' }; } return { kind: 'allow' }; - } + }, }, { name: 'nationSurlimit', requires: (ctx) => [{ kind: 'nation', id: ctx.nationId! }], test: (_ctx: ConstraintContext, view: StateView) => { const nation = view.get({ kind: 'nation', id: _ctx.nationId! }) as Nation | undefined; - const surlimit = (nation?.meta as any)?.surlimit ?? 0; + const surlimitRaw = nation?.meta.surlimit; + const surlimit = typeof surlimitRaw === 'number' ? surlimitRaw : 0; if (surlimit > 0) return { kind: 'deny', reason: '외교제한중입니다.' }; return { kind: 'allow' }; - } + }, }, { name: 'destNationSurlimit', requires: () => [{ kind: 'nation', id: args.destNationId }], - test: (ctx: ConstraintContext, view: StateView) => { + test: (_ctx: ConstraintContext, view: StateView) => { const destNation = view.get({ kind: 'nation', id: args.destNationId }) as Nation | undefined; - const surlimit = (destNation?.meta as any)?.surlimit ?? 0; + const surlimitRaw = destNation?.meta.surlimit; + const surlimit = typeof surlimitRaw === 'number' ? surlimitRaw : 0; if (surlimit > 0) return { kind: 'deny', reason: '상대국이 외교제한중입니다.' }; return { kind: 'allow' }; - } - } + }, + }, ]; } @@ -119,38 +125,53 @@ export class ActionDefinition< const broadcastMessage = `${destNation.name}${josaRo} 금${goldText} 쌀${riceText}을 지원했습니다.`; const effects: Array> = [ - createNationPatchEffect({ - gold: nation.gold - actualGold, - rice: nation.rice - actualRice, - meta: { - ...nation.meta, - surlimit: ((nation.meta as any)?.surlimit ?? 0) + POST_REQ_TURN, + createNationPatchEffect( + { + gold: nation.gold - actualGold, + rice: nation.rice - actualRice, + meta: { + ...nation.meta, + surlimit: Number(nation.meta.surlimit ?? 0) + POST_REQ_TURN, + }, }, - }, nation.id), - createNationPatchEffect({ - gold: destNation.gold + actualGold, - rice: destNation.rice + actualRice, - }, destNation.id), + nation.id + ), + createNationPatchEffect( + { + gold: destNation.gold + actualGold, + rice: destNation.rice + actualRice, + }, + destNation.id + ), // Global History Log - createLogEffect(`【${ACTION_NAME}】${nationName}에서 ${destNation.name}${josaRo} 물자를 지원합니다`, { - scope: LogScope.SYSTEM, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }), + createLogEffect( + `【${ACTION_NAME}】${nationName}에서 ${destNation.name}${josaRo} 물자를 지원합니다`, + { + scope: LogScope.SYSTEM, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + } + ), // Actor Nation History Log - createLogEffect(`${destNation.name}${josaRo} 금${goldText} 쌀${riceText}${josaUlRice} 지원`, { - scope: LogScope.NATION, - nationId: nation.id, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }), + createLogEffect( + `${destNation.name}${josaRo} 금${goldText} 쌀${riceText}${josaUlRice} 지원`, + { + scope: LogScope.NATION, + nationId: nation.id, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + } + ), // Dest Nation History Log - createLogEffect(`${nationName}로부터 금${goldText} 쌀${riceText}${josaUlRice} 지원 받음`, { - scope: LogScope.NATION, - nationId: destNation.id, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }), + createLogEffect( + `${nationName}로부터 금${goldText} 쌀${riceText}${josaUlRice} 지원 받음`, + { + scope: LogScope.NATION, + nationId: destNation.id, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + } + ), // General Action Log createLogEffect(broadcastMessage, { scope: LogScope.GENERAL, diff --git a/packages/logic/src/actions/turn/nation/che_증축.ts b/packages/logic/src/actions/turn/nation/che_증축.ts index 227aa70..2aeae2c 100644 --- a/packages/logic/src/actions/turn/nation/che_증축.ts +++ b/packages/logic/src/actions/turn/nation/che_증축.ts @@ -8,7 +8,11 @@ import { reqNationRice, } from '@sammo-ts/logic/constraints/presets.js'; import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js'; -import type { GeneralActionEffect, GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; +import type { + GeneralActionEffect, + GeneralActionOutcome, + GeneralActionResolveContext, +} from '@sammo-ts/logic/actions/engine.js'; import { createLogEffect, createNationPatchEffect, createCityPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; import { JosaUtil } from '@sammo-ts/common'; @@ -16,7 +20,7 @@ import type { TurnCommandEnv } from '@sammo-ts/logic/actions/turn/commandEnv.js' import type { NationTurnCommandSpec } from './index.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; -export interface ExpandCityArgs { } +export interface ExpandCityArgs {} export interface ExpandCityResolveContext< TriggerState extends GeneralTriggerState = GeneralTriggerState, @@ -38,7 +42,7 @@ export class ActionDefinition< public readonly key = 'che_증축'; public readonly name = ACTION_NAME; - constructor(private readonly env: TurnCommandEnv) { } + constructor(private readonly env: TurnCommandEnv) {} parseArgs(_raw: unknown): ExpandCityArgs | null { return {}; @@ -60,7 +64,7 @@ export class ActionDefinition< name: 'expandableCity', requires: (ctx) => [ { kind: 'nation', id: ctx.nationId! }, - { kind: 'city', id: 0 } + { kind: 'city', id: 0 }, ], test: (ctx: ConstraintContext, view: StateView) => { const nation = view.get({ kind: 'nation', id: ctx.nationId! }) as Nation | undefined; @@ -74,8 +78,8 @@ export class ActionDefinition< if (capitalCity.level >= 8) return { kind: 'deny', reason: '더이상 증축할 수 없습니다.' }; return { kind: 'allow' }; - } - } + }, + }, ]; } @@ -98,38 +102,53 @@ export class ActionDefinition< const josaYiNation = JosaUtil.pick(nationName, '이'); const effects: Array> = [ - createCityPatchEffect({ - level: capitalCity.level + 1, - populationMax: capitalCity.populationMax + POP_INCREASE, - agricultureMax: capitalCity.agricultureMax + DEVEL_INCREASE, - commerceMax: capitalCity.commerceMax + DEVEL_INCREASE, - securityMax: capitalCity.securityMax + DEVEL_INCREASE, - defenceMax: capitalCity.defenceMax + WALL_INCREASE, - wallMax: capitalCity.wallMax + WALL_INCREASE, - }, capitalCity.id), - createNationPatchEffect({ - gold: nation.gold - cost, - rice: nation.rice - cost, - }, nation.id), + createCityPatchEffect( + { + level: capitalCity.level + 1, + populationMax: capitalCity.populationMax + POP_INCREASE, + agricultureMax: capitalCity.agricultureMax + DEVEL_INCREASE, + commerceMax: capitalCity.commerceMax + DEVEL_INCREASE, + securityMax: capitalCity.securityMax + DEVEL_INCREASE, + defenceMax: capitalCity.defenceMax + WALL_INCREASE, + wallMax: capitalCity.wallMax + WALL_INCREASE, + }, + capitalCity.id + ), + createNationPatchEffect( + { + gold: nation.gold - cost, + rice: nation.rice - cost, + }, + nation.id + ), // Global Action Log - createLogEffect(`${generalName}${josaYi} ${destCityName}${josaUl} ${ACTION_NAME}하였습니다.`, { - scope: LogScope.SYSTEM, - category: LogCategory.ACTION, - format: LogFormat.PLAIN, - }), + createLogEffect( + `${generalName}${josaYi} ${destCityName}${josaUl} ${ACTION_NAME}하였습니다.`, + { + scope: LogScope.SYSTEM, + category: LogCategory.ACTION, + format: LogFormat.PLAIN, + } + ), // Global History Log - createLogEffect(`【${ACTION_NAME}】${nationName}${josaYiNation} ${destCityName}${josaUl} ${ACTION_NAME}하였습니다.`, { - scope: LogScope.SYSTEM, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }), + createLogEffect( + `【${ACTION_NAME}】${nationName}${josaYiNation} ${destCityName}${josaUl} ${ACTION_NAME}하였습니다.`, + { + scope: LogScope.SYSTEM, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + } + ), // Actor Nation History Log - createLogEffect(`${generalName}${josaYi} ${destCityName}${josaUl} ${ACTION_NAME}`, { - scope: LogScope.NATION, - nationId: nation.id, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }), + createLogEffect( + `${generalName}${josaYi} ${destCityName}${josaUl} ${ACTION_NAME}`, + { + scope: LogScope.NATION, + nationId: nation.id, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + } + ), // General Action Log createLogEffect(`${destCityName}${josaUl} ${ACTION_NAME}했습니다.`, { scope: LogScope.GENERAL, diff --git a/packages/logic/src/actions/turn/nation/che_천도.ts b/packages/logic/src/actions/turn/nation/che_천도.ts index 411fbe6..c8caa60 100644 --- a/packages/logic/src/actions/turn/nation/che_천도.ts +++ b/packages/logic/src/actions/turn/nation/che_천도.ts @@ -8,7 +8,11 @@ import { suppliedDestCity, } from '@sammo-ts/logic/constraints/presets.js'; import type { GeneralActionDefinition } from '@sammo-ts/logic/actions/definition.js'; -import type { GeneralActionEffect, GeneralActionOutcome, GeneralActionResolveContext } from '@sammo-ts/logic/actions/engine.js'; +import type { + GeneralActionEffect, + GeneralActionOutcome, + GeneralActionResolveContext, +} from '@sammo-ts/logic/actions/engine.js'; import { createLogEffect, createNationPatchEffect } from '@sammo-ts/logic/actions/engine.js'; import { LogCategory, LogFormat, LogScope } from '@sammo-ts/logic/logging/types.js'; import type { ActionContextBuilder } from '@sammo-ts/logic/actions/turn/actionContext.js'; @@ -30,11 +34,7 @@ export interface MoveCapitalResolveContext< const ACTION_NAME = '천도'; -const calcDistance = ( - fromCityId: number, - toCityId: number, - map: MapDefinition -): number => { +const calcDistance = (fromCityId: number, toCityId: number, map: MapDefinition): number => { if (fromCityId === toCityId) return 0; const connections = new Map(); @@ -68,7 +68,7 @@ export class ActionDefinition< public readonly key = 'che_천도'; public readonly name = ACTION_NAME; - constructor(private readonly env: TurnCommandEnv) { } + constructor(private readonly env: TurnCommandEnv) {} parseArgs(raw: unknown): MoveCapitalArgs | null { const data = raw as { destCityID?: number }; @@ -94,28 +94,31 @@ export class ActionDefinition< return { kind: 'deny', reason: '이미 수도입니다.' }; } return { kind: 'allow' }; - } + }, }, { name: 'reqMoveCapitalCost', requires: (ctx) => [ { kind: 'nation', id: ctx.nationId! }, - { kind: 'env', key: 'map' } + { kind: 'env', key: 'map' }, ], test: (ctx: ConstraintContext, view: StateView) => { const nation = view.get({ kind: 'nation', id: ctx.nationId! }) as Nation | undefined; const map = view.get({ kind: 'env', key: 'map' }) as MapDefinition | undefined; - if (!map || !nation || nation.capitalCityId === undefined || nation.capitalCityId === null) return { kind: 'allow' }; + if (!map || !nation || nation.capitalCityId === undefined || nation.capitalCityId === null) + return { kind: 'allow' }; const dist = calcDistance(nation.capitalCityId, args.destCityID, map); const cost = develcost * 5 * Math.pow(2, dist); - if (nation.gold < cost + 1000) return { kind: 'deny', reason: `금이 부족합니다. (필요: ${cost + 1000})` }; - if (nation.rice < cost + 1000) return { kind: 'deny', reason: `쌀이 부족합니다. (필요: ${cost + 1000})` }; + if (nation.gold < cost + 1000) + return { kind: 'deny', reason: `금이 부족합니다. (필요: ${cost + 1000})` }; + if (nation.rice < cost + 1000) + return { kind: 'deny', reason: `쌀이 부족합니다. (필요: ${cost + 1000})` }; return { kind: 'allow' }; - } - } + }, + }, ]; } @@ -140,30 +143,42 @@ export class ActionDefinition< const josaYiNation = JosaUtil.pick(nationName, '이'); const effects: Array> = [ - createNationPatchEffect({ - capitalCityId: args.destCityID, - gold: nation.gold - cost, - rice: nation.rice - cost, - }, nation.id), + createNationPatchEffect( + { + capitalCityId: args.destCityID, + gold: nation.gold - cost, + rice: nation.rice - cost, + }, + nation.id + ), // Global Action Log - createLogEffect(`${generalName}${josaYi} ${destCityName}${josaRo} ${ACTION_NAME}를 명령하였습니다.`, { - scope: LogScope.SYSTEM, - category: LogCategory.ACTION, - format: LogFormat.PLAIN, - }), + createLogEffect( + `${generalName}${josaYi} ${destCityName}${josaRo} ${ACTION_NAME}를 명령하였습니다.`, + { + scope: LogScope.SYSTEM, + category: LogCategory.ACTION, + format: LogFormat.PLAIN, + } + ), // Global History Log - createLogEffect(`【${ACTION_NAME}】${nationName}${josaYiNation} ${destCityName}${josaRo} ${ACTION_NAME}하였습니다.`, { - scope: LogScope.SYSTEM, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }), + createLogEffect( + `【${ACTION_NAME}】${nationName}${josaYiNation} ${destCityName}${josaRo} ${ACTION_NAME}하였습니다.`, + { + scope: LogScope.SYSTEM, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + } + ), // Actor Nation History Log - createLogEffect(`${generalName}${josaYi} ${destCityName}${josaRo} ${ACTION_NAME} 명령`, { - scope: LogScope.NATION, - nationId: nation.id, - category: LogCategory.HISTORY, - format: LogFormat.YEAR_MONTH, - }), + createLogEffect( + `${generalName}${josaYi} ${destCityName}${josaRo} ${ACTION_NAME} 명령`, + { + scope: LogScope.NATION, + nationId: nation.id, + category: LogCategory.HISTORY, + format: LogFormat.YEAR_MONTH, + } + ), // General Action Log createLogEffect(`${destCityName}${josaRo} ${ACTION_NAME}했습니다.`, { scope: LogScope.GENERAL, diff --git a/packages/logic/src/actions/turn/nation/index.ts b/packages/logic/src/actions/turn/nation/index.ts index 74d1734..74c15d3 100644 --- a/packages/logic/src/actions/turn/nation/index.ts +++ b/packages/logic/src/actions/turn/nation/index.ts @@ -66,7 +66,7 @@ export class NationTurnCommandLoader { constructor( private readonly importers: Record = defaultImporters - ) { } + ) {} async load(key: NationTurnCommandKey): Promise { const cached = this.cache.get(key); diff --git a/packages/logic/test/actions/turn/nation.test.ts b/packages/logic/test/actions/turn/nation.test.ts index 41656a8..f483dfd 100644 --- a/packages/logic/test/actions/turn/nation.test.ts +++ b/packages/logic/test/actions/turn/nation.test.ts @@ -2,6 +2,9 @@ import { describe, expect, it } from 'vitest'; import type { City, General, Nation } from '../../../src/domain/entities.js'; import { resolveGeneralAction } from '../../../src/actions/engine.js'; import { ActionDefinition as DeclareWarAction } from '../../../src/actions/turn/nation/che_선전포고.js'; +import { ActionDefinition as MoveCapitalAction } from '../../../src/actions/turn/nation/che_천도.js'; +import { ActionDefinition as ChangeNationNameAction } from '../../../src/actions/turn/nation/che_국호변경.js'; +import { ActionDefinition as ExpandCityAction } from '../../../src/actions/turn/nation/che_증축.js'; import { ActionDefinition as LastStandAction } from '../../../src/actions/turn/nation/che_필사즉생.js'; import { LogCategory, LogScope } from '../../../src/logging/types.js'; import type { MapDefinition } from '../../../src/world/types.js'; @@ -241,4 +244,91 @@ describe('Nation Actions', () => { expect(nation.meta.strategic_cmd_limit).toBeGreaterThan(0); }); }); + + describe('che_천도 (Move Capital)', () => { + it('changes nation capital city', () => { + const nation = buildNation(1); + const city1 = buildCity(1, 1); + const city2 = buildCity(2, 1); + const general = buildGeneral(1, 1, 1); + const env = { develCost: 100, baseGold: 100, baseRice: 100 }; + const definition = new MoveCapitalAction(env as any); + + const context = { + general, + nation, + city: city1, + destCity: city2, + rng: {} as any, + map: { + cities: [ + { id: 1, connections: [2] }, + { id: 2, connections: [1] }, + ], + } as any, + addLog: () => {}, + }; + + const resolution = definition.resolve(context as any, { destCityID: 2 }); + + expect(resolution.effects).toContainEqual( + expect.objectContaining({ + type: 'nation:patch', + patch: expect.objectContaining({ capitalCityId: 2 }), + }) + ); + }); + }); + + describe('che_국호변경 (Change Nation Name)', () => { + it('changes nation name', () => { + const nation = buildNation(1, 'OldName'); + const general = buildGeneral(1, 1, 1); + const definition = new ChangeNationNameAction(); + + const context = { + general, + nation, + rng: {} as any, + addLog: () => {}, + }; + + const resolution = definition.resolve(context as any, { nationName: 'NewName' }); + + expect(resolution.effects).toContainEqual( + expect.objectContaining({ + type: 'nation:patch', + patch: expect.objectContaining({ name: 'NewName' }), + }) + ); + }); + }); + + describe('che_증축 (City Expansion)', () => { + it('increases city level and stats', () => { + const nation = buildNation(1); + const city = buildCity(1, 1); + city.level = 2; + const general = buildGeneral(1, 1, 1); + const env = { develCost: 100, baseGold: 100, baseRice: 100 }; + const definition = new ExpandCityAction(env as any); + + const context = { + general, + nation, + capitalCity: city, + rng: {} as any, + addLog: () => {}, + }; + + const resolution = definition.resolve(context as any, {}); + + expect(resolution.effects).toContainEqual( + expect.objectContaining({ + type: 'city:patch', + patch: expect.objectContaining({ level: 3 }), + }) + ); + }); + }); });