import type { GeneralTriggerState } from '@sammo-ts/logic/domain/entities.js'; import type { GeneralActionModule } from '@sammo-ts/logic/actionModules/general.js'; import { createGeneralActionEvent, dispatchGeneralActionEventHandlers, type GeneralActionEvent, type GeneralActionEventContext, type GeneralActionEventType, } from '@sammo-ts/logic/actionModules/events.js'; import { GeneralTriggerCaller, type GeneralActionContext } from '@sammo-ts/logic/triggers/general.js'; import type { GeneralStatName, TriggerDomesticActionType, TriggerDomesticVarType, TriggerNationalIncomeType, TriggerStrategicActionType, TriggerStrategicVarType, WarStatName, } from '@sammo-ts/logic/actionModules/types.js'; import type { WarActionContext, WarActionModule } from '@sammo-ts/logic/war/actions.js'; import { WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js'; import type { WarUnit } from '@sammo-ts/logic/war/units.js'; import type { ItemModule, ItemModuleExport } from './types.js'; import { listEquippedItemKeys } from './utils.js'; import { removeEquippedItem } from './inventory.js'; export const ITEM_KEYS = [ 'che_간파_노군입산부', 'che_격노_구정신단경', 'che_계략_삼략', 'che_계략_육도', 'che_계략_이추', 'che_계략_향낭', 'che_공성_묵자', 'che_내정_납금박산로', 'che_농성_위공자병법', 'che_농성_주서음부', 'che_능력치_무력_두강주', 'che_능력치_지력_이강주', 'che_능력치_통솔_보령압주', 'che_명마_01_노기', 'che_명마_02_조랑', 'che_명마_03_노새', 'che_명마_04_나귀', 'che_명마_05_갈색마', 'che_명마_06_흑색마', 'che_명마_07_기주마', 'che_명마_07_백마', 'che_명마_07_백상', 'che_명마_07_오환마', 'che_명마_08_양주마', 'che_명마_08_흉노마', 'che_명마_09_과하마', 'che_명마_09_의남백마', 'che_명마_10_대완마', 'che_명마_10_옥추마', 'che_명마_11_서량마', 'che_명마_11_화종마', 'che_명마_12_사륜거', 'che_명마_12_옥란백용구', 'che_명마_13_적로', 'che_명마_13_절영', 'che_명마_14_적란마', 'che_명마_14_조황비전', 'che_명마_15_적토마', 'che_명마_15_한혈마', 'che_명성_구석', 'che_무기_01_단도', 'che_무기_02_단궁', 'che_무기_03_단극', 'che_무기_04_목검', 'che_무기_05_죽창', 'che_무기_06_소부', 'che_무기_07_동추', 'che_무기_07_맥궁', 'che_무기_07_철쇄', 'che_무기_07_철편', 'che_무기_08_유성추', 'che_무기_08_철질여골', 'che_무기_09_동호비궁', 'che_무기_09_쌍철극', 'che_무기_10_대부', 'che_무기_10_삼첨도', 'che_무기_11_고정도', 'che_무기_11_이광궁', 'che_무기_12_철척사모', 'che_무기_12_칠성검', 'che_무기_13_사모', 'che_무기_13_양유기궁', 'che_무기_14_방천화극', 'che_무기_14_언월도', 'che_무기_15_의천검', 'che_무기_15_청홍검', 'che_보물_도기', 'che_부적_태현청생부', 'che_불굴_상편', 'che_사기_초선화', 'che_사기_춘화첩', 'che_사기_탁주', 'che_상성보정_과실주', 'che_서적_01_효경전', 'che_서적_02_회남자', 'che_서적_03_변도론', 'che_서적_04_건상역주', 'che_서적_05_여씨춘추', 'che_서적_06_사민월령', 'che_서적_07_논어', 'che_서적_07_사마법', 'che_서적_07_위료자', 'che_서적_07_한서', 'che_서적_08_사기', 'che_서적_08_전론', 'che_서적_09_역경', 'che_서적_09_장자', 'che_서적_10_구국론', 'che_서적_10_시경', 'che_서적_11_상군서', 'che_서적_11_춘추전', 'che_서적_12_맹덕신서', 'che_서적_12_산해경', 'che_서적_13_관자', 'che_서적_13_병법24편', 'che_서적_14_오자병법', 'che_서적_14_한비자', 'che_서적_15_노자', 'che_서적_15_손자병법', 'che_숙련_동작', 'che_약탈_옥벽', 'che_위압_조목삭', 'che_의술_상한잡병론', 'che_의술_정력견혈산', 'che_의술_청낭서', 'che_의술_태평청령', 'che_저격_매화수전', 'che_저격_비도', 'che_저격_수극', 'che_저지_삼황내문', 'che_전략_평만지장도', 'che_조달_주판', 'che_진압_박혁론', 'che_집중_전국책', 'che_징병_낙주', 'che_척사_오악진형도', 'che_치료_환약', 'che_필살_둔갑천서', 'che_행동_서촉지형도', 'che_환술_논어집해', 'che_회피_태평요술', 'che_훈련_단결도', 'che_훈련_철벽서', 'che_훈련_청주', 'event_전투특기_격노', 'event_전투특기_견고', 'event_전투특기_공성', 'event_전투특기_궁병', 'event_전투특기_귀병', 'event_전투특기_기병', 'event_전투특기_돌격', 'event_전투특기_무쌍', 'event_전투특기_반계', 'event_전투특기_보병', 'event_전투특기_신산', 'event_전투특기_신중', 'event_전투특기_위압', 'event_전투특기_의술', 'event_전투특기_저격', 'event_전투특기_집중', 'event_전투특기_징병', 'event_전투특기_척사', 'event_전투특기_필살', 'event_전투특기_환술', 'event_충차', ] as const; export type ItemKey = (typeof ITEM_KEYS)[number]; export type ItemImporter = () => Promise; const defaultImporters: Record = { che_간파_노군입산부: async () => import('./che_간파_노군입산부.js'), che_격노_구정신단경: async () => import('./che_격노_구정신단경.js'), che_계략_삼략: async () => import('./che_계략_삼략.js'), che_계략_육도: async () => import('./che_계략_육도.js'), che_계략_이추: async () => import('./che_계략_이추.js'), che_계략_향낭: async () => import('./che_계략_향낭.js'), che_공성_묵자: async () => import('./che_공성_묵자.js'), che_내정_납금박산로: async () => import('./che_내정_납금박산로.js'), che_농성_위공자병법: async () => import('./che_농성_위공자병법.js'), che_농성_주서음부: async () => import('./che_농성_주서음부.js'), che_능력치_무력_두강주: async () => import('./che_능력치_무력_두강주.js'), che_능력치_지력_이강주: async () => import('./che_능력치_지력_이강주.js'), che_능력치_통솔_보령압주: async () => import('./che_능력치_통솔_보령압주.js'), che_명마_01_노기: async () => import('./che_명마_01_노기.js'), che_명마_02_조랑: async () => import('./che_명마_02_조랑.js'), che_명마_03_노새: async () => import('./che_명마_03_노새.js'), che_명마_04_나귀: async () => import('./che_명마_04_나귀.js'), che_명마_05_갈색마: async () => import('./che_명마_05_갈색마.js'), che_명마_06_흑색마: async () => import('./che_명마_06_흑색마.js'), che_명마_07_기주마: async () => import('./che_명마_07_기주마.js'), che_명마_07_백마: async () => import('./che_명마_07_백마.js'), che_명마_07_백상: async () => import('./che_명마_07_백상.js'), che_명마_07_오환마: async () => import('./che_명마_07_오환마.js'), che_명마_08_양주마: async () => import('./che_명마_08_양주마.js'), che_명마_08_흉노마: async () => import('./che_명마_08_흉노마.js'), che_명마_09_과하마: async () => import('./che_명마_09_과하마.js'), che_명마_09_의남백마: async () => import('./che_명마_09_의남백마.js'), che_명마_10_대완마: async () => import('./che_명마_10_대완마.js'), che_명마_10_옥추마: async () => import('./che_명마_10_옥추마.js'), che_명마_11_서량마: async () => import('./che_명마_11_서량마.js'), che_명마_11_화종마: async () => import('./che_명마_11_화종마.js'), che_명마_12_사륜거: async () => import('./che_명마_12_사륜거.js'), che_명마_12_옥란백용구: async () => import('./che_명마_12_옥란백용구.js'), che_명마_13_적로: async () => import('./che_명마_13_적로.js'), che_명마_13_절영: async () => import('./che_명마_13_절영.js'), che_명마_14_적란마: async () => import('./che_명마_14_적란마.js'), che_명마_14_조황비전: async () => import('./che_명마_14_조황비전.js'), che_명마_15_적토마: async () => import('./che_명마_15_적토마.js'), che_명마_15_한혈마: async () => import('./che_명마_15_한혈마.js'), che_명성_구석: async () => import('./che_명성_구석.js'), che_무기_01_단도: async () => import('./che_무기_01_단도.js'), che_무기_02_단궁: async () => import('./che_무기_02_단궁.js'), che_무기_03_단극: async () => import('./che_무기_03_단극.js'), che_무기_04_목검: async () => import('./che_무기_04_목검.js'), che_무기_05_죽창: async () => import('./che_무기_05_죽창.js'), che_무기_06_소부: async () => import('./che_무기_06_소부.js'), che_무기_07_동추: async () => import('./che_무기_07_동추.js'), che_무기_07_맥궁: async () => import('./che_무기_07_맥궁.js'), che_무기_07_철쇄: async () => import('./che_무기_07_철쇄.js'), che_무기_07_철편: async () => import('./che_무기_07_철편.js'), che_무기_08_유성추: async () => import('./che_무기_08_유성추.js'), che_무기_08_철질여골: async () => import('./che_무기_08_철질여골.js'), che_무기_09_동호비궁: async () => import('./che_무기_09_동호비궁.js'), che_무기_09_쌍철극: async () => import('./che_무기_09_쌍철극.js'), che_무기_10_대부: async () => import('./che_무기_10_대부.js'), che_무기_10_삼첨도: async () => import('./che_무기_10_삼첨도.js'), che_무기_11_고정도: async () => import('./che_무기_11_고정도.js'), che_무기_11_이광궁: async () => import('./che_무기_11_이광궁.js'), che_무기_12_철척사모: async () => import('./che_무기_12_철척사모.js'), che_무기_12_칠성검: async () => import('./che_무기_12_칠성검.js'), che_무기_13_사모: async () => import('./che_무기_13_사모.js'), che_무기_13_양유기궁: async () => import('./che_무기_13_양유기궁.js'), che_무기_14_방천화극: async () => import('./che_무기_14_방천화극.js'), che_무기_14_언월도: async () => import('./che_무기_14_언월도.js'), che_무기_15_의천검: async () => import('./che_무기_15_의천검.js'), che_무기_15_청홍검: async () => import('./che_무기_15_청홍검.js'), che_보물_도기: async () => import('./che_보물_도기.js'), che_부적_태현청생부: async () => import('./che_부적_태현청생부.js'), che_불굴_상편: async () => import('./che_불굴_상편.js'), che_사기_초선화: async () => import('./che_사기_초선화.js'), che_사기_춘화첩: async () => import('./che_사기_춘화첩.js'), che_사기_탁주: async () => import('./che_사기_탁주.js'), che_상성보정_과실주: async () => import('./che_상성보정_과실주.js'), che_서적_01_효경전: async () => import('./che_서적_01_효경전.js'), che_서적_02_회남자: async () => import('./che_서적_02_회남자.js'), che_서적_03_변도론: async () => import('./che_서적_03_변도론.js'), che_서적_04_건상역주: async () => import('./che_서적_04_건상역주.js'), che_서적_05_여씨춘추: async () => import('./che_서적_05_여씨춘추.js'), che_서적_06_사민월령: async () => import('./che_서적_06_사민월령.js'), che_서적_07_논어: async () => import('./che_서적_07_논어.js'), che_서적_07_사마법: async () => import('./che_서적_07_사마법.js'), che_서적_07_위료자: async () => import('./che_서적_07_위료자.js'), che_서적_07_한서: async () => import('./che_서적_07_한서.js'), che_서적_08_사기: async () => import('./che_서적_08_사기.js'), che_서적_08_전론: async () => import('./che_서적_08_전론.js'), che_서적_09_역경: async () => import('./che_서적_09_역경.js'), che_서적_09_장자: async () => import('./che_서적_09_장자.js'), che_서적_10_구국론: async () => import('./che_서적_10_구국론.js'), che_서적_10_시경: async () => import('./che_서적_10_시경.js'), che_서적_11_상군서: async () => import('./che_서적_11_상군서.js'), che_서적_11_춘추전: async () => import('./che_서적_11_춘추전.js'), che_서적_12_맹덕신서: async () => import('./che_서적_12_맹덕신서.js'), che_서적_12_산해경: async () => import('./che_서적_12_산해경.js'), che_서적_13_관자: async () => import('./che_서적_13_관자.js'), che_서적_13_병법24편: async () => import('./che_서적_13_병법24편.js'), che_서적_14_오자병법: async () => import('./che_서적_14_오자병법.js'), che_서적_14_한비자: async () => import('./che_서적_14_한비자.js'), che_서적_15_노자: async () => import('./che_서적_15_노자.js'), che_서적_15_손자병법: async () => import('./che_서적_15_손자병법.js'), che_숙련_동작: async () => import('./che_숙련_동작.js'), che_약탈_옥벽: async () => import('./che_약탈_옥벽.js'), che_위압_조목삭: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_위압.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('che_위압_조목삭', traitModule, { rawName: '조목삭', name: '조목삭(위압)', cost: 200, buyable: false, reqSecu: 0, }), }; }, che_의술_상한잡병론: async () => import('./che_의술_상한잡병론.js'), che_의술_정력견혈산: async () => import('./che_의술_정력견혈산.js'), che_의술_청낭서: async () => import('./che_의술_청낭서.js'), che_의술_태평청령: async () => import('./che_의술_태평청령.js'), che_저격_매화수전: async () => import('./che_저격_매화수전.js'), che_저격_비도: async () => import('./che_저격_비도.js'), che_저격_수극: async () => import('./che_저격_수극.js'), che_저지_삼황내문: async () => import('./che_저지_삼황내문.js'), che_전략_평만지장도: async () => import('./che_전략_평만지장도.js'), che_조달_주판: async () => import('./che_조달_주판.js'), che_진압_박혁론: async () => import('./che_진압_박혁론.js'), che_집중_전국책: async () => import('./che_집중_전국책.js'), che_징병_낙주: async () => import('./che_징병_낙주.js'), che_척사_오악진형도: async () => import('./che_척사_오악진형도.js'), che_치료_환약: async () => import('./che_치료_환약.js'), che_필살_둔갑천서: async () => import('./che_필살_둔갑천서.js'), che_행동_서촉지형도: async () => import('./che_행동_서촉지형도.js'), che_환술_논어집해: async () => import('./che_환술_논어집해.js'), che_회피_태평요술: async () => import('./che_회피_태평요술.js'), che_훈련_단결도: async () => import('./che_훈련_단결도.js'), che_훈련_철벽서: async () => import('./che_훈련_철벽서.js'), che_훈련_청주: async () => import('./che_훈련_청주.js'), event_전투특기_격노: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_격노.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_격노', traitModule) }; }, event_전투특기_견고: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_견고.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_견고', traitModule) }; }, event_전투특기_공성: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_공성.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_공성', traitModule) }; }, event_전투특기_궁병: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_궁병.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_궁병', traitModule) }; }, event_전투특기_귀병: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_귀병.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_귀병', traitModule) }; }, event_전투특기_기병: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_기병.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_기병', traitModule) }; }, event_전투특기_돌격: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_돌격.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_돌격', traitModule) }; }, event_전투특기_무쌍: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_무쌍.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_무쌍', traitModule) }; }, event_전투특기_반계: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_반계.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_반계', traitModule) }; }, event_전투특기_보병: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_보병.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_보병', traitModule) }; }, event_전투특기_신산: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_신산.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_신산', traitModule) }; }, event_전투특기_신중: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_신중.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_신중', traitModule) }; }, event_전투특기_위압: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_위압.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_위압', traitModule) }; }, event_전투특기_의술: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_의술.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_의술', traitModule) }; }, event_전투특기_저격: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_저격.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_저격', traitModule) }; }, event_전투특기_집중: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_집중.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_집중', traitModule) }; }, event_전투특기_징병: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_징병.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_징병', traitModule) }; }, event_전투특기_척사: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_척사.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_척사', traitModule) }; }, event_전투특기_필살: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_필살.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_필살', traitModule) }; }, event_전투특기_환술: async () => { const [{ traitModule }, { createEventBattleTraitItemModule }] = await Promise.all([ import('../actionModules/traits/war/che_환술.js'), import('./eventBattleTrait.js'), ]); return { itemModule: createEventBattleTraitItemModule('event_전투특기_환술', traitModule) }; }, event_충차: async () => import('./event_충차.js'), }; export const isItemKey = (value: string): value is ItemKey => ITEM_KEYS.includes(value as ItemKey); export class ItemLoader { private readonly cache = new Map>(); constructor(private readonly importers: Record = defaultImporters) {} async load(key: ItemKey): Promise { const cached = this.cache.get(key); if (cached) { return cached; } const importer = this.importers[key]; if (!importer) { throw new Error(`Unknown item key: ${key}`); } const loading = importer().then((module) => { if (!('itemModule' in module)) { throw new Error(`Missing itemModule for item: ${key}`); } const resolved: ItemModule = { ...module.itemModule, // Legacy defines the finite unique pool as every non-buyable // scenario item; keep that invariant authoritative instead of // relying on duplicated per-module flags. unique: !module.itemModule.buyable, }; if (resolved.key !== key) { throw new Error(`Item key mismatch: expected ${key}, got ${resolved.key}`); } return resolved; }); this.cache.set(key, loading); return loading; } } export const loadItemModules = async ( keys: ItemKey[], loader: ItemLoader = new ItemLoader() ): Promise => { const modules: ItemModule[] = []; const seen = new Set(); for (const key of keys) { if (seen.has(key)) { continue; } seen.add(key); modules.push(await loader.load(key)); } return modules; }; export type ItemModuleRegistry = Map< string, ItemModule >; export const createItemModuleRegistry = ( modules: ItemModule[] ): ItemModuleRegistry => { const registry: ItemModuleRegistry = new Map(); for (const module of modules) { registry.set(module.key, module); } return registry; }; class ItemGeneralActionRouter { constructor(private readonly registry: ItemModuleRegistry) {} private resolveModules(context: GeneralActionContext): Array> { const keys = listEquippedItemKeys(context.general); const modules: Array> = []; for (const key of keys) { const module = this.registry.get(key); if (module) { modules.push(module); } } return modules; } getPreTurnExecuteTriggerList( context: GeneralActionContext ): GeneralTriggerCaller | null { const caller = new GeneralTriggerCaller(); for (const module of this.resolveModules(context)) { const triggers = module.getPreTurnExecuteTriggerList?.(context); if (triggers) { caller.merge(triggers); } } return caller.isEmpty() ? null : caller; } onCalcDomestic( context: GeneralActionContext, turnType: TriggerDomesticActionType, varType: TriggerDomesticVarType, value: number, aux?: unknown ): number { let current = value; for (const module of this.resolveModules(context)) { if (!module.onCalcDomestic) { continue; } current = module.onCalcDomestic(context, turnType, varType, current, aux); } return current; } onCalcStat( context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown ): number { let current = value; for (const module of this.resolveModules(context)) { if (!module.onCalcStat) { continue; } current = module.onCalcStat(context, statName, current, aux); } return current; } onCalcOpposeStat( context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown ): number { let current = value; for (const module of this.resolveModules(context)) { if (!module.onCalcOpposeStat) { continue; } current = module.onCalcOpposeStat(context, statName, current, aux); } return current; } onCalcStrategic( context: GeneralActionContext, turnType: TriggerStrategicActionType, varType: TriggerStrategicVarType, value: number ): number { let current = value; for (const module of this.resolveModules(context)) { if (!module.onCalcStrategic) { continue; } current = module.onCalcStrategic(context, turnType, varType, current); } return current; } onCalcNationalIncome( context: GeneralActionContext, type: TriggerNationalIncomeType, amount: number ): number { let current = amount; for (const module of this.resolveModules(context)) { if (!module.onCalcNationalIncome) { continue; } current = module.onCalcNationalIncome(context, type, current); } return current; } handleEvent( context: GeneralActionEventContext, event: GeneralActionEvent ): GeneralActionEvent { let current = event; for (const module of this.resolveModules(context)) { current = dispatchGeneralActionEventHandlers(module.eventHandlers, context, current); if (current.type === 'strategy.succeeded' && module.consumeOnStrategySuccess) { const removed = removeEquippedItem(context.general, module.slot); if (removed?.itemKey === module.key) { const strategyEvent = current as GeneralActionEvent<'strategy.succeeded', TriggerState>; current = createGeneralActionEvent('strategy.succeeded', { consumedItems: [...strategyEvent.payload.consumedItems, module.key], }) as GeneralActionEvent; } } } return current; } } class ItemWarActionRouter< TriggerState extends GeneralTriggerState = GeneralTriggerState, > implements WarActionModule { constructor(private readonly registry: ItemModuleRegistry) {} private resolveModules(context: WarActionContext): Array> { const keys = listEquippedItemKeys(context.general); const modules: Array> = []; for (const key of keys) { const module = this.registry.get(key); if (module) { modules.push(module); } } return modules; } getBattleInitTriggerList(context: WarActionContext): WarTriggerCaller | null { const caller = new WarTriggerCaller(); for (const module of this.resolveModules(context)) { const triggers = module.getBattleInitTriggerList?.(context); if (triggers) { caller.merge(triggers); } } return caller.isEmpty() ? null : caller; } getBattlePhaseTriggerList(context: WarActionContext): WarTriggerCaller | null { const caller = new WarTriggerCaller(); for (const module of this.resolveModules(context)) { const triggers = module.getBattlePhaseTriggerList?.(context); if (triggers) { caller.merge(triggers); } } return caller.isEmpty() ? null : caller; } onCalcStat( context: WarActionContext, statName: WarStatName, value: number | [number, number], aux?: unknown ): number | [number, number] { let current: number | [number, number] = value; for (const module of this.resolveModules(context)) { if (!module.onCalcStat) { continue; } current = module.onCalcStat(context, statName, current, aux); } return current; } onCalcOpposeStat( context: WarActionContext, statName: WarStatName, value: number | [number, number], aux?: unknown ): number | [number, number] { let current: number | [number, number] = value; for (const module of this.resolveModules(context)) { if (!module.onCalcOpposeStat) { continue; } current = module.onCalcOpposeStat(context, statName, current, aux); } return current; } getWarPowerMultiplier( context: WarActionContext, unit: WarUnit, oppose: WarUnit ): [number, number] { let attack = 1; let defence = 1; for (const module of this.resolveModules(context)) { if (!module.getWarPowerMultiplier) { continue; } const [attMul, defMul] = module.getWarPowerMultiplier(context, unit, oppose); attack *= attMul; defence *= defMul; } return [attack, defence]; } } export const createItemActionModules = ( registry: ItemModuleRegistry ): { general: ReadonlyArray>; war: WarActionModule[] } => ({ general: [new ItemGeneralActionRouter(registry)], war: [new ItemWarActionRouter(registry)], }); export type { ItemModule, ItemModuleExport, ItemSlot } from './types.js'; export { cloneItemInventory, ensureItemInventory, equipNewItem, getEquippedItemInstance, projectItemSlots, readItemInventoryFromMeta, removeEquippedItem, withSerializedItemInventory, } from './inventory.js';