feat: port scenario 903 select-pool flow

This commit is contained in:
2026-07-30 23:27:59 +00:00
parent 9bd057456b
commit 115218ded8
80 changed files with 6859 additions and 48 deletions
+10 -2
View File
@@ -16,7 +16,9 @@ import { createOfficerLevelActionModules } from './officerLevel.js';
import {
createTraitCatalog,
DOMESTIC_TRAIT_KEYS,
EVENT_DOMESTIC_TRAIT_KEYS,
loadDomesticTraitModules,
loadEventDomesticTraitModules,
loadNationTraitModules,
loadPersonalityTraitModules,
loadWarTraitModules,
@@ -87,14 +89,20 @@ export const loadActionModuleBundle = async <TriggerState extends GeneralTrigger
unitSet?: UnitSetDefinition,
scenarioEffect?: ScenarioEffectKey | null
): Promise<ActionModuleBundle<TriggerState>> => {
const [domestic, war, personality, nation, itemModules] = await Promise.all([
const [domestic, eventDomestic, war, personality, nation, itemModules] = await Promise.all([
loadDomesticTraitModules([...DOMESTIC_TRAIT_KEYS]),
loadEventDomesticTraitModules([...EVENT_DOMESTIC_TRAIT_KEYS]),
loadWarTraitModules([...WAR_TRAIT_KEYS]),
loadPersonalityTraitModules([...PERSONALITY_TRAIT_KEYS]),
loadNationTraitModules([...NATION_TRAIT_KEYS]),
loadItemModules([...ITEM_KEYS]) as Promise<ItemModule<TriggerState>[]>,
]);
const traitCatalog = createTraitCatalog<TriggerState>({ domestic, war, personality, nation });
const traitCatalog = createTraitCatalog<TriggerState>({
domestic: [...domestic, ...eventDomestic],
war,
personality,
nation,
});
const officer = createOfficerLevelActionModules<TriggerState>();
const items = createItemActionModules(createItemModuleRegistry(itemModules));
const inherit = createInheritBuffModules();
@@ -0,0 +1,126 @@
import type { TraitModule } from '@sammo-ts/logic/actionModules/traits/types.js';
import {
isWarTraitKey,
type WarTraitKey,
WarTraitLoader,
} from '@sammo-ts/logic/actionModules/traits/war/index.js';
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
import { che_부상무효 } from '@sammo-ts/logic/war/triggers/che_견고.js';
export const EVENT_DOMESTIC_TRAIT_KEYS = [
'che_event_귀병',
'che_event_신산',
'che_event_환술',
'che_event_집중',
'che_event_신중',
'che_event_반계',
'che_event_보병',
'che_event_궁병',
'che_event_기병',
'che_event_공성',
'che_event_돌격',
'che_event_무쌍',
'che_event_견고',
'che_event_위압',
'che_event_저격',
'che_event_필살',
'che_event_징병',
'che_event_의술',
'che_event_격노',
'che_event_척사',
] as const;
export type EventDomesticTraitKey = (typeof EVENT_DOMESTIC_TRAIT_KEYS)[number];
export type EventDomesticTraitModule = TraitModule;
export const EVENT_GYEONGO_RAISE_TYPE = BaseWarUnitTrigger.TYPE_ITEM;
export const isEventDomesticTraitKey = (value: string): value is EventDomesticTraitKey =>
EVENT_DOMESTIC_TRAIT_KEYS.includes(value as EventDomesticTraitKey);
const resolveWarKey = (key: EventDomesticTraitKey): WarTraitKey => {
const warKey = key.replace(/^che_event_/, 'che_');
if (!isWarTraitKey(warKey)) {
throw new Error(`Event domestic trait has no canonical war trait: ${key}`);
}
return warKey;
};
const withRefEventOverrides = (
key: EventDomesticTraitKey,
canonical: TraitModule
): EventDomesticTraitModule => {
const { selection: _selection, ...behavior } = canonical;
const alias: EventDomesticTraitModule = {
...behavior,
key,
kind: 'domestic',
};
if (key === 'che_event_무쌍' && canonical.getWarPowerMultiplier) {
alias.getWarPowerMultiplier = (context, unit, oppose) => {
const general =
'getGeneral' in unit &&
typeof (unit as { getGeneral?: unknown }).getGeneral === 'function'
? (
unit as typeof unit & {
getGeneral: () => { role: { specialWar: string | null } };
}
).getGeneral()
: null;
return general?.role.specialWar === canonical.key
? [1, 1]
: canonical.getWarPowerMultiplier!(context, unit, oppose);
};
}
if (key === 'che_event_견고') {
alias.getBattleInitTriggerList = (context) => {
if (!context.unit) return null;
return new WarTriggerCaller(
new che_부상무효(context.unit, EVENT_GYEONGO_RAISE_TYPE)
);
};
alias.getBattlePhaseTriggerList = (context) => {
if (!context.unit) return null;
return new WarTriggerCaller(
new che_부상무효(context.unit, EVENT_GYEONGO_RAISE_TYPE)
);
};
}
return alias;
};
export class EventDomesticTraitLoader {
private readonly cache = new Map<EventDomesticTraitKey, Promise<EventDomesticTraitModule>>();
constructor(private readonly warLoader = new WarTraitLoader()) {}
async load(key: EventDomesticTraitKey): Promise<EventDomesticTraitModule> {
const cached = this.cache.get(key);
if (cached) {
return cached;
}
const loading = this.warLoader
.load(resolveWarKey(key))
.then((canonical) => withRefEventOverrides(key, canonical));
this.cache.set(key, loading);
return loading;
}
}
export const loadEventDomesticTraitModules = async (
keys: EventDomesticTraitKey[],
loader = new EventDomesticTraitLoader()
): Promise<EventDomesticTraitModule[]> => {
const modules: EventDomesticTraitModule[] = [];
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;
};
@@ -3,6 +3,7 @@ export * from './requirements.js';
export * from './selector.js';
export * from './catalog.js';
export * from './domestic/index.js';
export * from './eventDomestic/index.js';
export * from './war/index.js';
export * from './personality/index.js';
export * from './nation/index.js';
@@ -3,9 +3,12 @@ import type { GeneralStatName, WarStatName } from '@sammo-ts/logic/actionModules
import type { WarActionContext } from '@sammo-ts/logic/war/actions.js';
import type { TraitModule } from '@sammo-ts/logic/actionModules/traits/types.js';
import { TraitRequirement, TraitWeightType } from '../requirements.js';
import { WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
import { BaseWarUnitTrigger, WarTriggerCaller } from '@sammo-ts/logic/war/triggers.js';
import { che_부상무효 } from '@sammo-ts/logic/war/triggers/che_견고.js';
export const GYEONGO_RAISE_TYPE =
BaseWarUnitTrigger.TYPE_NONE + BaseWarUnitTrigger.TYPE_DEDUP_TYPE_BASE * 404;
function onCalcStat(context: GeneralActionContext, statName: GeneralStatName, value: number, aux?: unknown): number;
function onCalcStat(
context: WarActionContext,
@@ -45,11 +48,11 @@ export const traitModule: TraitModule = {
}) as TraitModule['onCalcOpposeStat'],
getBattleInitTriggerList: (_context) => {
if (!_context.unit) return null;
return new WarTriggerCaller(new che_부상무효(_context.unit));
return new WarTriggerCaller(new che_부상무효(_context.unit, GYEONGO_RAISE_TYPE));
},
getBattlePhaseTriggerList: (_context) => {
if (!_context.unit) return null;
return new WarTriggerCaller(new che_부상무효(_context.unit));
return new WarTriggerCaller(new che_부상무효(_context.unit, GYEONGO_RAISE_TYPE));
},
getWarPowerMultiplier: (_context, _unit, _oppose) => {
return [1, 0.9];
@@ -3,8 +3,8 @@ import { BaseWarUnitTrigger } from '@sammo-ts/logic/war/triggers.js';
import type { WarUnit } from '@sammo-ts/logic/war/units.js';
export class che_부상무효 extends BaseWarUnitTrigger {
constructor(unit: WarUnit) {
super(unit, TriggerPriority.Begin + 200);
constructor(unit: WarUnit, raiseType = BaseWarUnitTrigger.TYPE_NONE) {
super(unit, TriggerPriority.Begin + 200, raiseType);
}
protected actionWar(