fix: 시나리오별 장비 매매 품목 범위를 보존
현재 시나리오 allItems를 API 선택지와 턴 실행 검증에 함께 적용한다. 빈 설정은 Ref 기본 장비 24종으로 복원하고 명시된 비급 시나리오는 기존 품목을 유지한다.
This commit is contained in:
@@ -61,6 +61,7 @@ export interface TurnCommandEnv {
|
||||
npcSeizureMessageProb?: number;
|
||||
maxResourceActionAmount: number;
|
||||
itemCatalog?: Record<string, TurnCommandItemCatalogEntry>;
|
||||
purchasableItemKeys?: ReadonlySet<string>;
|
||||
generalActionModules?: RefOrderedActionStack<GeneralActionModule>;
|
||||
warActionModules?: RefOrderedActionStack<WarActionModule>;
|
||||
nationTraitModules?: Array<NationTraitModule>;
|
||||
|
||||
@@ -79,7 +79,11 @@ export class ActionDefinition<
|
||||
if (!item) {
|
||||
return null;
|
||||
}
|
||||
if (item.slot !== itemType || !item.buyable) {
|
||||
if (
|
||||
item.slot !== itemType ||
|
||||
!item.buyable ||
|
||||
(this.env.purchasableItemKeys !== undefined && !this.env.purchasableItemKeys.has(itemCode))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return args;
|
||||
|
||||
@@ -113,6 +113,56 @@ const LEGACY_UNIQUE_ITEM_KEYS: Readonly<Record<ItemModule['slot'], readonly stri
|
||||
],
|
||||
};
|
||||
|
||||
const LEGACY_DEFAULT_BUYABLE_ITEM_KEYS: Readonly<Record<ItemModule['slot'], readonly string[]>> = {
|
||||
horse: [
|
||||
'che_명마_01_노기',
|
||||
'che_명마_02_조랑',
|
||||
'che_명마_03_노새',
|
||||
'che_명마_04_나귀',
|
||||
'che_명마_05_갈색마',
|
||||
'che_명마_06_흑색마',
|
||||
],
|
||||
weapon: [
|
||||
'che_무기_01_단도',
|
||||
'che_무기_02_단궁',
|
||||
'che_무기_03_단극',
|
||||
'che_무기_04_목검',
|
||||
'che_무기_05_죽창',
|
||||
'che_무기_06_소부',
|
||||
],
|
||||
book: [
|
||||
'che_서적_01_효경전',
|
||||
'che_서적_02_회남자',
|
||||
'che_서적_03_변도론',
|
||||
'che_서적_04_건상역주',
|
||||
'che_서적_05_여씨춘추',
|
||||
'che_서적_06_사민월령',
|
||||
],
|
||||
item: ['che_치료_환약', 'che_저격_수극', 'che_사기_탁주', 'che_훈련_청주', 'che_계략_이추', 'che_계략_향낭'],
|
||||
};
|
||||
|
||||
/**
|
||||
* Ref 장비 매매는 GameConst::$allItems에 있고 수량이 0 이하인 구매 가능 아이템만
|
||||
* 노출합니다. 생략/옛 문자열 빈 객체는 GameConstBase의 기본 24종으로 복원합니다.
|
||||
*/
|
||||
export const resolveLegacyPurchasableItemKeys = (configConst: Record<string, unknown>): ReadonlySet<string> => {
|
||||
const { allItems } = resolveUniqueConfig(configConst);
|
||||
const hasExplicitPool = Object.values(allItems).some((entries) => Object.keys(entries ?? {}).length > 0);
|
||||
if (!hasExplicitPool) {
|
||||
return new Set(Object.values(LEGACY_DEFAULT_BUYABLE_ITEM_KEYS).flat());
|
||||
}
|
||||
|
||||
const result = new Set<string>();
|
||||
for (const entries of Object.values(allItems)) {
|
||||
for (const [itemKey, count] of Object.entries(entries ?? {})) {
|
||||
if (count <= 0) {
|
||||
result.add(itemKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export const buildLegacyDefaultUniqueItemPool = (itemRegistry: Map<string, ItemModule>): UniqueItemPool => {
|
||||
const pool: UniqueItemPool = { horse: {}, weapon: {}, book: {}, item: {} };
|
||||
for (const slot of ['horse', 'weapon', 'book', 'item'] as const) {
|
||||
|
||||
@@ -252,6 +252,37 @@ describe('typed item lifecycle events', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('시나리오 상점 목록에 없는 전역 구매 가능 아이템을 거부한다', () => {
|
||||
const itemKey = 'event_전투특기_격노';
|
||||
const catalog: Record<string, TurnCommandItemCatalogEntry> = {
|
||||
[itemKey]: {
|
||||
slot: 'item',
|
||||
name: '격노의 비급',
|
||||
rawName: '격노의 비급',
|
||||
cost: 100,
|
||||
reqSecu: 0,
|
||||
buyable: true,
|
||||
unique: false,
|
||||
},
|
||||
};
|
||||
const denied = new TradeItemAction({
|
||||
...BASE_ENV,
|
||||
itemCatalog: catalog,
|
||||
purchasableItemKeys: new Set(),
|
||||
});
|
||||
const allowed = new TradeItemAction({
|
||||
...BASE_ENV,
|
||||
itemCatalog: catalog,
|
||||
purchasableItemKeys: new Set([itemKey]),
|
||||
});
|
||||
|
||||
expect(denied.parseArgs({ itemType: 'item', itemCode: itemKey })).toBeNull();
|
||||
expect(allowed.parseArgs({ itemType: 'item', itemCode: itemKey })).toEqual({
|
||||
itemType: 'item',
|
||||
itemCode: itemKey,
|
||||
});
|
||||
});
|
||||
|
||||
it('계략 성공 capability만 소비하며 typed 결과로 소비 item을 반환한다', () => {
|
||||
const general = makeGeneral('che_계략_이추');
|
||||
const itemModules = createItemActionModules(createItemModuleRegistry([strategyItemModule]));
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { resolveLegacyCompatibleUniqueConfig } from '../src/rewards/legacyUniqueItemPool.js';
|
||||
import {
|
||||
resolveLegacyCompatibleUniqueConfig,
|
||||
resolveLegacyPurchasableItemKeys,
|
||||
} from '../src/rewards/legacyUniqueItemPool.js';
|
||||
|
||||
describe('legacy-compatible unique item pool', () => {
|
||||
it.each([undefined, {}, '{}'] as const)('restores the Ref default pool when allItems is %j', async (allItems) => {
|
||||
@@ -27,4 +30,29 @@ describe('legacy-compatible unique item pool', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it.each([undefined, {}, '{}'] as const)('restores the Ref default shop items when allItems is %j', (allItems) => {
|
||||
const keys = resolveLegacyPurchasableItemKeys(allItems === undefined ? {} : { allItems });
|
||||
|
||||
expect(keys.size).toBe(24);
|
||||
expect(keys.has('che_명마_01_노기')).toBe(true);
|
||||
expect(keys.has('che_치료_환약')).toBe(true);
|
||||
expect(keys.has('event_전투특기_격노')).toBe(false);
|
||||
});
|
||||
|
||||
it('uses only non-limited entries from an explicit scenario shop pool', () => {
|
||||
const keys = resolveLegacyPurchasableItemKeys({
|
||||
allItems: {
|
||||
weapon: {
|
||||
che_무기_01_단도: 0,
|
||||
che_무기_12_칠성검: 2,
|
||||
},
|
||||
item: {
|
||||
event_전투특기_격노: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect([...keys]).toEqual(['che_무기_01_단도', 'event_전투특기_격노']);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user