fix(logic): 전투 특기의 비전투 커맨드 효과를 복구

모병에서도 실제 행동명을 특기 계산에 전달해 징병 특기의 훈련과 사기를 레거시 값으로 맞춘다. 의술의 환자별 기록 소유권과 PLAIN 형식, 다인 치료 요약 대상도 복구하고 전투 특기 비전투 hook 회귀를 추가한다.
This commit is contained in:
2026-08-21 02:59:15 +00:00
parent 2a73f80b52
commit fe438c5195
7 changed files with 348 additions and 14 deletions
@@ -13,8 +13,10 @@ import {
loadEventDomesticTraitModules,
loadDomesticTraitModules,
loadWarTraitModules,
WAR_TRAIT_KEYS,
} from '../src/actionModules/traits/index.js';
import { ActionLogger } from '../src/logging/actionLogger.js';
import { LogFormat } from '../src/logging/types.js';
import { WarActionPipeline } from '../src/war/actions.js';
import { WarCrewType } from '../src/war/crewType.js';
import { createWarTriggerEnv } from '../src/war/triggers.js';
@@ -164,6 +166,23 @@ const buildUnitSet = (): UnitSetDefinition => ({
});
describe('trait modules', () => {
it('keeps the Ref inventory of battle traits with non-battle hooks', async () => {
const war = await loadWarTraitModules([...WAR_TRAIT_KEYS]);
expect(war.filter((module) => module.onCalcDomestic).map((module) => module.key)).toEqual([
'che_귀병',
'che_신산',
'che_보병',
'che_궁병',
'che_기병',
'che_공성',
'che_징병',
]);
expect(war.filter((module) => module.getPreTurnExecuteTriggerList).map((module) => module.key)).toEqual([
'che_의술',
]);
});
it('loads trait modules by key', async () => {
const domestic = await loadDomesticTraitModules(['che_인덕', 'che_발명']);
const war = await loadWarTraitModules(['che_의술', 'che_징병']);
@@ -286,6 +305,56 @@ describe('trait modules', () => {
expect(higherIdPatient.injury).toBe(30);
});
it('writes Ref-compatible patient and healer logs for 의술 city healing', async () => {
const war = await loadWarTraitModules(['che_의술']);
const registry = createTraitCatalog({ war });
const pipeline = new GeneralActionPipeline(createTraitModules(registry).general);
const healer = buildGeneral({
name: '의사',
role: {
personality: null,
specialDomestic: null,
specialWar: 'che_의술',
items: { horse: null, weapon: null, book: null, item: null },
},
});
const firstPatient = buildGeneral({ id: 2, name: '환자갑', injury: 20 });
const lastPatient = buildGeneral({ id: 3, name: '환자을', injury: 20 });
const worldView = {
listGeneralsByCity: () => [lastPatient, healer, firstPatient],
listGenerals: () => [lastPatient, healer, firstPatient],
};
const rng: RandomGenerator = {
nextFloat1: () => 0,
nextBool: () => true,
nextInt: (minInclusive: number) => minInclusive,
};
const healerLogs: string[] = [];
const patientLogs: Array<{ generalId: number; message: string; format: LogFormat | undefined }> = [];
const healerFormats: Array<LogFormat | undefined> = [];
const context = createGeneralTriggerContext({
general: healer,
rng,
worldView,
log: {
push: (message, options) => {
healerLogs.push(message);
healerFormats.push(options?.format);
},
pushForGeneral: (generalId, message, options) =>
patientLogs.push({ generalId, message, format: options?.format }),
},
});
pipeline.getPreTurnExecuteTriggerList(context).fire(context, {});
expect(patientLogs.map((log) => log.generalId)).toEqual([2, 3]);
expect(patientLogs.every((log) => log.message.includes('<Y>의사</>'))).toBe(true);
expect(patientLogs.every((log) => log.format === LogFormat.PLAIN)).toBe(true);
expect(healerLogs.at(-1)).toContain('<Y>환자을</> 외 <C>1</>명');
expect(healerFormats.every((format) => format === LogFormat.PLAIN)).toBe(true);
});
it('activates 의술 battle trigger and reduces damage', async () => {
const domestic = await loadDomesticTraitModules(['che_인덕', 'che_발명']);
const war = await loadWarTraitModules(['che_의술', 'che_징병']);