merge: 유산 경매 후보 종류 정렬을 main에 반영
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
isWarTraitKey,
|
||||
} from '@sammo-ts/logic';
|
||||
import type { InheritBuffType } from '@sammo-ts/logic';
|
||||
import type { ItemSlot } from '@sammo-ts/logic';
|
||||
import { simpleSerialize } from '@sammo-ts/logic/war/utils.js';
|
||||
import { resolveLegacyCompatibleUniqueConfig } from '@sammo-ts/logic/rewards/legacyUniqueItemPool.js';
|
||||
import {
|
||||
@@ -39,6 +40,8 @@ const BUFF_KEYS: InheritBuffType[] = [
|
||||
'warMagicTrialProbOppose',
|
||||
];
|
||||
|
||||
const UNIQUE_ITEM_SLOT_ORDER: readonly ItemSlot[] = ['horse', 'weapon', 'book', 'item'];
|
||||
|
||||
const BUFF_LABELS: Record<InheritBuffType, string> = {
|
||||
warAvoidRatio: '회피 확률 증가',
|
||||
warCriticalRatio: '필살 확률 증가',
|
||||
@@ -79,7 +82,8 @@ const loadAvailableUniqueItems = async (worldState: WorldStateRow) => {
|
||||
const loader = new ItemLoader();
|
||||
const { allItems } = await resolveLegacyCompatibleUniqueConfig(configConst, loader);
|
||||
const enabledKeys: Array<Parameters<ItemLoader['load']>[0]> = [];
|
||||
for (const entries of Object.values(allItems)) {
|
||||
for (const slot of UNIQUE_ITEM_SLOT_ORDER) {
|
||||
const entries = allItems[slot] ?? {};
|
||||
for (const [key, amount] of Object.entries(asRecord(entries))) {
|
||||
if (asNumber(amount, 0) !== 0 && isItemKey(key)) {
|
||||
enabledKeys.push(key);
|
||||
@@ -94,10 +98,11 @@ const loadAvailableUniqueItems = async (worldState: WorldStateRow) => {
|
||||
name: item.name,
|
||||
rawName: item.rawName,
|
||||
info: item.info ?? '',
|
||||
slot: item.slot,
|
||||
};
|
||||
})
|
||||
);
|
||||
return items.sort((left, right) => left.name.localeCompare(right.name, 'ko'));
|
||||
return items;
|
||||
};
|
||||
|
||||
const resolveWorld = async (ctx: { db: { worldState: { findFirst: () => Promise<unknown> } } }) => {
|
||||
|
||||
@@ -218,6 +218,32 @@ describe('inherit router actor and permission boundaries', () => {
|
||||
}
|
||||
);
|
||||
|
||||
it('orders unique auction candidates by Ref slot order and preserves order within each slot', async () => {
|
||||
const fixture = buildContext({
|
||||
configConst: {
|
||||
allItems: {
|
||||
item: { che_보물_도기: 1 },
|
||||
book: { che_서적_07_논어: 1 },
|
||||
weapon: { che_무기_12_칠성검: 1 },
|
||||
horse: {
|
||||
che_명마_07_백마: 1,
|
||||
che_명마_07_기주마: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const status = await appRouter.createCaller(fixture.context).inherit.getStatus();
|
||||
|
||||
expect(status.availableUnique.map(({ key, slot }) => ({ key, slot }))).toEqual([
|
||||
{ key: 'che_명마_07_백마', slot: 'horse' },
|
||||
{ key: 'che_명마_07_기주마', slot: 'horse' },
|
||||
{ key: 'che_무기_12_칠성검', slot: 'weapon' },
|
||||
{ key: 'che_서적_07_논어', slot: 'book' },
|
||||
{ key: 'che_보물_도기', slot: 'item' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('loads the first inheritance-log page without an out-of-range integer cursor', async () => {
|
||||
const createdAt = new Date('2026-07-26T00:00:00Z');
|
||||
const fixture = buildContext({
|
||||
|
||||
@@ -6,6 +6,7 @@ import { trpc } from '../utils/trpc';
|
||||
type InheritStatus = Awaited<ReturnType<typeof trpc.inherit.getStatus.query>>;
|
||||
type InheritLog = Awaited<ReturnType<typeof trpc.inherit.getLogs.query>>[number];
|
||||
type JoinConfig = Awaited<ReturnType<typeof trpc.join.getConfig.query>>;
|
||||
type UniqueItemSlot = InheritStatus['availableUnique'][number]['slot'];
|
||||
|
||||
type BuffKey =
|
||||
| 'warAvoidRatio'
|
||||
@@ -67,6 +68,14 @@ const pointOrder = [
|
||||
'betting',
|
||||
] as const;
|
||||
|
||||
const uniqueItemSlotOrder: readonly UniqueItemSlot[] = ['horse', 'weapon', 'book', 'item'];
|
||||
const uniqueItemSlotLabels: Record<UniqueItemSlot, string> = {
|
||||
horse: '명마',
|
||||
weapon: '무기',
|
||||
book: '서적',
|
||||
item: '도구',
|
||||
};
|
||||
|
||||
const pointHelp: Record<string, string> = {
|
||||
previous: '이전에 물려받은 포인트입니다.',
|
||||
lived_month: '살아남은 기간입니다. (1개월 단위)',
|
||||
@@ -196,6 +205,15 @@ const specialNameMap = computed(() => {
|
||||
const selectedSpecialWarInfo = computed(
|
||||
() => status.value?.availableSpecialWar.find((entry) => entry.key === nextSpecialKey.value)?.info ?? ''
|
||||
);
|
||||
const availableUniqueGroups = computed(() =>
|
||||
uniqueItemSlotOrder
|
||||
.map((slot) => ({
|
||||
slot,
|
||||
label: uniqueItemSlotLabels[slot],
|
||||
items: status.value?.availableUnique.filter((item) => item.slot === slot) ?? [],
|
||||
}))
|
||||
.filter((group) => group.items.length > 0)
|
||||
);
|
||||
|
||||
const buffCost = (key: BuffKey, target: number): number => {
|
||||
const points = status.value?.inheritConst.inheritBuffPoints ?? [0, 0, 0, 0, 0, 0];
|
||||
@@ -518,9 +536,11 @@ onMounted(() => {
|
||||
<label for="specific-unique">유니크 경매</label>
|
||||
<select id="specific-unique" v-model="uniqueForm.itemId">
|
||||
<option disabled value="">유니크 선택</option>
|
||||
<option v-for="item in status.availableUnique" :key="item.key" :value="item.key">
|
||||
{{ item.name }}
|
||||
</option>
|
||||
<optgroup v-for="group in availableUniqueGroups" :key="group.slot" :label="group.label">
|
||||
<option v-for="item in group.items" :key="item.key" :value="item.key">
|
||||
{{ item.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div class="control-row">
|
||||
|
||||
@@ -81,17 +81,33 @@ const statusFixture = {
|
||||
resetLevels: { resetSpecialWar: 0, resetTurnTime: 0 },
|
||||
availableSpecialWar: [{ key: 'che_선봉', name: '선봉', info: '공격에 유리합니다.' }],
|
||||
availableUnique: [
|
||||
{
|
||||
key: 'che_명마_07_백마',
|
||||
name: '백마(+7)',
|
||||
rawName: '백마',
|
||||
info: '기동력을 올려주는 유니크 명마입니다.',
|
||||
slot: 'horse',
|
||||
},
|
||||
{
|
||||
key: 'che_무기_12_칠성검',
|
||||
name: '칠성검(+12)',
|
||||
rawName: '칠성검',
|
||||
info: '무력을 올려주는 유니크 무기입니다.',
|
||||
slot: 'weapon',
|
||||
},
|
||||
{
|
||||
key: 'che_서적_07_논어',
|
||||
name: '논어(+7)',
|
||||
rawName: '논어',
|
||||
info: '지력을 올려주는 유니크 서적입니다.',
|
||||
slot: 'book',
|
||||
},
|
||||
{
|
||||
key: 'che_보물_도기',
|
||||
name: '도기',
|
||||
rawName: '도기',
|
||||
info: '전투를 돕는 유니크 도구입니다.',
|
||||
slot: 'item',
|
||||
},
|
||||
],
|
||||
availableTargetGenerals: [{ id: 8, name: '조조' }],
|
||||
@@ -197,7 +213,21 @@ test.describe('inheritance management legacy parity', () => {
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await page.goto(gameUrl);
|
||||
await expect(page.locator('#container')).toBeVisible();
|
||||
await expect(page.locator('#specific-unique')).toHaveValue('che_무기_12_칠성검');
|
||||
await expect(page.locator('#specific-unique')).toHaveValue('che_명마_07_백마');
|
||||
await expect(page.locator('#specific-unique optgroup')).toHaveCount(4);
|
||||
expect(
|
||||
await page.locator('#specific-unique optgroup').evaluateAll((groups) =>
|
||||
groups.map((group) => ({
|
||||
label: group.getAttribute('label'),
|
||||
values: [...group.querySelectorAll('option')].map((option) => option.value),
|
||||
}))
|
||||
)
|
||||
).toEqual([
|
||||
{ label: '명마', values: ['che_명마_07_백마'] },
|
||||
{ label: '무기', values: ['che_무기_12_칠성검'] },
|
||||
{ label: '서적', values: ['che_서적_07_논어'] },
|
||||
{ label: '도구', values: ['che_보물_도기'] },
|
||||
]);
|
||||
|
||||
const desktop = await page.evaluate(() => {
|
||||
const rect = (selector: string) => {
|
||||
|
||||
Reference in New Issue
Block a user