From 4efc9a7679ce7c38dd96408ee3c001f8ab2a2e90 Mon Sep 17 00:00:00 2001 From: hided62 Date: Mon, 24 Aug 2026 08:14:53 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EA=B5=B0?= =?UTF-8?q?=EC=A3=BC=20=EC=9E=90=EB=8F=99=20=EC=84=A0=EC=A0=84=ED=8F=AC?= =?UTF-8?q?=EA=B3=A0=EB=A5=BC=20=EB=B6=84=EB=A6=AC=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/game-api/src/router/general/index.ts | 2 ++ app/game-api/test/inGameMenuPermissions.test.ts | 11 +++++++++++ app/game-engine/src/turn/ai/policies.ts | 5 +++-- app/game-engine/src/turn/commandRegistry.ts | 1 + app/game-engine/src/turn/worldCommandHandler.ts | 1 + .../test/generalAiLegacyDecisionParity.test.ts | 11 ++++++++++- app/game-engine/test/myInformationCommands.test.ts | 2 ++ app/game-frontend/e2e/inGameMenus.spec.ts | 13 +++++++++++-- app/game-frontend/src/views/MyPageView.vue | 13 ++++++++++++- packages/common/src/turnDaemon/types.ts | 1 + 10 files changed, 54 insertions(+), 6 deletions(-) diff --git a/app/game-api/src/router/general/index.ts b/app/game-api/src/router/general/index.ts index 2a3fcca4..418656a9 100644 --- a/app/game-api/src/router/general/index.ts +++ b/app/game-api/src/router/general/index.ts @@ -56,6 +56,7 @@ const zGeneralSettings = z.object({ use_treatment: z.number().int().optional(), use_auto_nation_turn: z.number().int().optional(), use_auto_nation_diplomacy: z.number().int().min(0).max(1).optional(), + use_auto_nation_war: z.number().int().min(0).max(1).optional(), use_auto_nation_promotion: z.number().int().min(0).max(1).optional(), use_auto_nation_finance: z.number().int().min(0).max(1).optional(), use_auto_nation_capital: z.number().int().min(0).max(1).optional(), @@ -218,6 +219,7 @@ const resolveUserSettings = (meta: Record) => { // Ref가 NPC 군주에게만 수행하던 국가 운영은 사용자 군주에게 opt-in이다. // 누락된 값은 신규 게임과 기존 장수 모두 안전한 기본값(사용 안함)으로 해석한다. use_auto_nation_diplomacy: readNumber(readSetting('use_auto_nation_diplomacy'), 0), + use_auto_nation_war: readNumber(readSetting('use_auto_nation_war'), 0), use_auto_nation_promotion: readNumber(readSetting('use_auto_nation_promotion'), 0), use_auto_nation_finance: readNumber(readSetting('use_auto_nation_finance'), 0), use_auto_nation_capital: readNumber(readSetting('use_auto_nation_capital'), 0), diff --git a/app/game-api/test/inGameMenuPermissions.test.ts b/app/game-api/test/inGameMenuPermissions.test.ts index 17856e5f..1ae5ada0 100644 --- a/app/game-api/test/inGameMenuPermissions.test.ts +++ b/app/game-api/test/inGameMenuPermissions.test.ts @@ -585,6 +585,7 @@ describe('in-game my information ownership', () => { use_treatment: 21, use_auto_nation_turn: 1, use_auto_nation_diplomacy: 0, + use_auto_nation_war: 0, use_auto_nation_promotion: 0, use_auto_nation_finance: 0, use_auto_nation_capital: 0, @@ -600,6 +601,16 @@ describe('in-game my information ownership', () => { expect(fixture.db.general.update).not.toHaveBeenCalled(); }); + it('rejects an invalid automatic war setting before dispatching it to ENGINE', async () => { + const requestCommand = vi.fn(async () => ({ type: 'setMySetting', ok: true, generalId: 7 })); + const fixture = createContext({ requestCommand }); + + await expect( + appRouter.createCaller(fixture.context).general.setMySetting({ use_auto_nation_war: 2 }) + ).rejects.toMatchObject({ code: 'BAD_REQUEST' }); + expect(requestCommand).not.toHaveBeenCalled(); + }); + it('sends settings directly to ENGINE without creating an API input event', async () => { const transaction = vi.fn(async () => { throw new Error('API transaction must not run'); diff --git a/app/game-engine/src/turn/ai/policies.ts b/app/game-engine/src/turn/ai/policies.ts index e205afec..ea669aad 100644 --- a/app/game-engine/src/turn/ai/policies.ts +++ b/app/game-engine/src/turn/ai/policies.ts @@ -61,10 +61,11 @@ export const AVAILABLE_INSTANT_TURN: Record = { NPC전방발령: true, }; -export type UserRulerAutomationFeature = 'diplomacy' | 'promotion' | 'finance' | 'capital'; +export type UserRulerAutomationFeature = 'diplomacy' | 'war' | 'promotion' | 'finance' | 'capital'; const USER_RULER_AUTOMATION_META_KEY = { diplomacy: 'use_auto_nation_diplomacy', + war: 'use_auto_nation_war', promotion: 'use_auto_nation_promotion', finance: 'use_auto_nation_finance', capital: 'use_auto_nation_capital', @@ -72,7 +73,7 @@ const USER_RULER_AUTOMATION_META_KEY = { const USER_RULER_ACTION_FEATURE: Readonly> = { 불가침제의: 'diplomacy', - 선전포고: 'diplomacy', + 선전포고: 'war', 천도: 'capital', }; diff --git a/app/game-engine/src/turn/commandRegistry.ts b/app/game-engine/src/turn/commandRegistry.ts index adf16d54..4f15dad3 100644 --- a/app/game-engine/src/turn/commandRegistry.ts +++ b/app/game-engine/src/turn/commandRegistry.ts @@ -137,6 +137,7 @@ const zSetMySetting = z.object({ use_treatment: z.number().int().optional(), use_auto_nation_turn: z.number().int().optional(), use_auto_nation_diplomacy: z.number().int().optional(), + use_auto_nation_war: z.number().int().optional(), use_auto_nation_promotion: z.number().int().optional(), use_auto_nation_finance: z.number().int().optional(), use_auto_nation_capital: z.number().int().optional(), diff --git a/app/game-engine/src/turn/worldCommandHandler.ts b/app/game-engine/src/turn/worldCommandHandler.ts index e1e69a84..e02b7cd6 100644 --- a/app/game-engine/src/turn/worldCommandHandler.ts +++ b/app/game-engine/src/turn/worldCommandHandler.ts @@ -1779,6 +1779,7 @@ async function handleSetMySetting( } for (const key of [ 'use_auto_nation_diplomacy', + 'use_auto_nation_war', 'use_auto_nation_promotion', 'use_auto_nation_finance', 'use_auto_nation_capital', diff --git a/app/game-engine/test/generalAiLegacyDecisionParity.test.ts b/app/game-engine/test/generalAiLegacyDecisionParity.test.ts index cab0554d..d1c76704 100644 --- a/app/game-engine/test/generalAiLegacyDecisionParity.test.ts +++ b/app/game-engine/test/generalAiLegacyDecisionParity.test.ts @@ -718,6 +718,7 @@ describe('legacy NPC user-chief promotion parity', () => { it('keeps user-ruler duties individually disabled until each setting is enabled', () => { const ruler = makePromotionGeneral({ id: 1, officerLevel: 12, npcState: 0, meta: { killturn: 0 } }); expect(canUseAutomatedNationAction(ruler, '선전포고')).toBe(false); + expect(canUseAutomatedNationAction(ruler, '불가침제의')).toBe(false); expect(canUseAutomatedNationAction(ruler, '천도')).toBe(false); expect(canUseRulerAutomation(ruler, 'finance')).toBe(false); @@ -728,9 +729,17 @@ describe('legacy NPC user-chief promotion parity', () => { use_auto_nation_finance: 1, }; expect(canUseAutomatedNationAction(ruler, '불가침제의')).toBe(true); - expect(canUseAutomatedNationAction(ruler, '선전포고')).toBe(true); + expect(canUseAutomatedNationAction(ruler, '선전포고')).toBe(false); expect(canUseAutomatedNationAction(ruler, '천도')).toBe(true); expect(canUseRulerAutomation(ruler, 'finance')).toBe(true); + + ruler.meta = { + ...ruler.meta, + use_auto_nation_diplomacy: 0, + use_auto_nation_war: 1, + }; + expect(canUseAutomatedNationAction(ruler, '불가침제의')).toBe(false); + expect(canUseAutomatedNationAction(ruler, '선전포고')).toBe(true); }); it('honors the existing automatic nation-turn master switch for user chiefs only', () => { diff --git a/app/game-engine/test/myInformationCommands.test.ts b/app/game-engine/test/myInformationCommands.test.ts index 3b0d728c..fd5b7206 100644 --- a/app/game-engine/test/myInformationCommands.test.ts +++ b/app/game-engine/test/myInformationCommands.test.ts @@ -223,6 +223,7 @@ describe('my information world commands', () => { use_treatment: 200, use_auto_nation_turn: 0, use_auto_nation_diplomacy: 1, + use_auto_nation_war: 1, use_auto_nation_promotion: 1, use_auto_nation_finance: 1, use_auto_nation_capital: 1, @@ -239,6 +240,7 @@ describe('my information world commands', () => { use_treatment: 100, use_auto_nation_turn: 0, use_auto_nation_diplomacy: 1, + use_auto_nation_war: 1, use_auto_nation_promotion: 1, use_auto_nation_finance: 1, use_auto_nation_capital: 1, diff --git a/app/game-frontend/e2e/inGameMenus.spec.ts b/app/game-frontend/e2e/inGameMenus.spec.ts index ea13a384..a6d5c015 100644 --- a/app/game-frontend/e2e/inGameMenus.spec.ts +++ b/app/game-frontend/e2e/inGameMenus.spec.ts @@ -276,6 +276,7 @@ const myGeneral = (state: FixtureState) => ({ use_treatment: 21, use_auto_nation_turn: 1, use_auto_nation_diplomacy: 0, + use_auto_nation_war: 0, use_auto_nation_promotion: 0, use_auto_nation_finance: 0, use_auto_nation_capital: 0, @@ -1342,11 +1343,18 @@ test('내 정보&설정 keeps desktop density and becomes a 390px horizontal-ide ]); const rulerAutomation = page.locator('.ruler-automation-settings'); await expect(rulerAutomation).toBeVisible(); - const diplomacyAutomation = page.getByRole('checkbox', { name: '자동 외교 (불가침 제의·선전포고)' }); + const diplomacyAutomation = page.getByRole('checkbox', { name: '자동 외교 (불가침 제의)' }); + const warAutomation = page.getByRole('checkbox', { name: '자동 선전포고' }); const promotionAutomation = page.getByRole('checkbox', { name: '자동 수뇌 임명' }); const financeAutomation = page.getByRole('checkbox', { name: '자동 세율·지급률 조정' }); const capitalAutomation = page.getByRole('checkbox', { name: '자동 천도' }); - for (const checkbox of [diplomacyAutomation, promotionAutomation, financeAutomation, capitalAutomation]) { + for (const checkbox of [ + diplomacyAutomation, + warAutomation, + promotionAutomation, + financeAutomation, + capitalAutomation, + ]) { await expect(checkbox).not.toBeChecked(); await checkbox.check(); } @@ -1410,6 +1418,7 @@ test('내 정보&설정 keeps desktop density and becomes a 390px horizontal-ide expect(state.settingMutations[0]).not.toHaveProperty('generalId'); expect(state.settingMutations[0]).toMatchObject({ use_auto_nation_diplomacy: 1, + use_auto_nation_war: 1, use_auto_nation_promotion: 1, use_auto_nation_finance: 1, use_auto_nation_capital: 1, diff --git a/app/game-frontend/src/views/MyPageView.vue b/app/game-frontend/src/views/MyPageView.vue index ea9e1748..6f8d56d4 100644 --- a/app/game-frontend/src/views/MyPageView.vue +++ b/app/game-frontend/src/views/MyPageView.vue @@ -33,6 +33,7 @@ type SettingForm = { use_treatment: number; use_auto_nation_turn: number; use_auto_nation_diplomacy: number; + use_auto_nation_war: number; use_auto_nation_promotion: number; use_auto_nation_finance: number; use_auto_nation_capital: number; @@ -69,6 +70,7 @@ const form = reactive({ use_treatment: 10, use_auto_nation_turn: 1, use_auto_nation_diplomacy: 0, + use_auto_nation_war: 0, use_auto_nation_promotion: 0, use_auto_nation_finance: 0, use_auto_nation_capital: 0, @@ -432,7 +434,16 @@ onMounted(() => { :true-value="1" :false-value="0" /> - 자동 외교 (불가침 제의·선전포고) + 자동 외교 (불가침 제의) + +