Files
core2026/packages/logic/src/items/index.ts
T

767 lines
34 KiB
TypeScript

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<ItemModuleExport>;
const defaultImporters: Record<ItemKey, ItemImporter> = {
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<ItemKey, Promise<ItemModule>>();
constructor(private readonly importers: Record<ItemKey, ItemImporter> = defaultImporters) {}
async load(key: ItemKey): Promise<ItemModule> {
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<ItemModule[]> => {
const modules: ItemModule[] = [];
const seen = new Set<string>();
for (const key of keys) {
if (seen.has(key)) {
continue;
}
seen.add(key);
modules.push(await loader.load(key));
}
return modules;
};
export type ItemModuleRegistry<TriggerState extends GeneralTriggerState = GeneralTriggerState> = Map<
string,
ItemModule<TriggerState>
>;
export const createItemModuleRegistry = <TriggerState extends GeneralTriggerState = GeneralTriggerState>(
modules: ItemModule<TriggerState>[]
): ItemModuleRegistry<TriggerState> => {
const registry: ItemModuleRegistry<TriggerState> = new Map();
for (const module of modules) {
registry.set(module.key, module);
}
return registry;
};
class ItemGeneralActionRouter<TriggerState extends GeneralTriggerState = GeneralTriggerState> {
constructor(private readonly registry: ItemModuleRegistry<TriggerState>) {}
private resolveModules(context: GeneralActionContext<TriggerState>): Array<ItemModule<TriggerState>> {
const keys = listEquippedItemKeys(context.general);
const modules: Array<ItemModule<TriggerState>> = [];
for (const key of keys) {
const module = this.registry.get(key);
if (module) {
modules.push(module);
}
}
return modules;
}
getPreTurnExecuteTriggerList(
context: GeneralActionContext<TriggerState>
): GeneralTriggerCaller<TriggerState> | null {
const caller = new GeneralTriggerCaller<TriggerState>();
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<TriggerState>,
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<TriggerState>,
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<TriggerState>,
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<TriggerState>,
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<TriggerState>,
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<K extends GeneralActionEventType>(
context: GeneralActionEventContext<K, TriggerState>,
event: GeneralActionEvent<K, TriggerState>
): GeneralActionEvent<K, TriggerState> {
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<TriggerState, 'strategy.succeeded'>('strategy.succeeded', {
consumedItems: [...strategyEvent.payload.consumedItems, module.key],
}) as GeneralActionEvent<K, TriggerState>;
}
}
}
return current;
}
}
class ItemWarActionRouter<
TriggerState extends GeneralTriggerState = GeneralTriggerState,
> implements WarActionModule<TriggerState> {
constructor(private readonly registry: ItemModuleRegistry<TriggerState>) {}
private resolveModules(context: WarActionContext<TriggerState>): Array<ItemModule<TriggerState>> {
const keys = listEquippedItemKeys(context.general);
const modules: Array<ItemModule<TriggerState>> = [];
for (const key of keys) {
const module = this.registry.get(key);
if (module) {
modules.push(module);
}
}
return modules;
}
getBattleInitTriggerList(context: WarActionContext<TriggerState>): 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<TriggerState>): 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<TriggerState>,
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<TriggerState>,
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<TriggerState>,
unit: WarUnit<TriggerState>,
oppose: WarUnit<TriggerState>
): [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 = <TriggerState extends GeneralTriggerState = GeneralTriggerState>(
registry: ItemModuleRegistry<TriggerState>
): { general: ReadonlyArray<GeneralActionModule<TriggerState>>; war: WarActionModule<TriggerState>[] } => ({
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';