fix(game-ui): 세력일람 병력을 추정 정보로 교체

This commit is contained in:
2026-08-29 14:43:52 +00:00
parent 52a75ca9b7
commit 254d321e8b
4 changed files with 301 additions and 55 deletions
+58 -12
View File
@@ -19,7 +19,21 @@ const nationDirectory = [
power: 2000,
capitalCityId: 2,
generalCount: 1,
totalCrew: 1_200,
forceEstimate: {
totalGeneralCount: 1,
userCombatGeneralCount: 1,
userTroops: 8_000,
npcCombatGeneralCount: 0,
npcTroops: 0,
inactiveGeneralCount: 0,
},
generalGroups: [
{
type: '만능',
label: '만능장',
generals: [{ id: 20, name: '유비', npcState: 0, inactive: false, accessGrade: 'normal' }],
},
],
cityCount: 1,
officers: Array.from({ length: 8 }, (_, index) => ({
officerLevel: 12 - index,
@@ -39,7 +53,26 @@ const nationDirectory = [
power: 1000,
capitalCityId: 1,
generalCount: 2,
totalCrew: 2_500,
forceEstimate: {
totalGeneralCount: 2,
userCombatGeneralCount: 1,
userTroops: 9_000,
npcCombatGeneralCount: 0,
npcTroops: 0,
inactiveGeneralCount: 1,
},
generalGroups: [
{
type: '무',
label: '무장',
generals: [{ id: 10, name: '조조', npcState: 0, inactive: false, accessGrade: 'high' }],
},
{
type: '지',
label: '지장',
generals: [{ id: 11, name: '순욱', npcState: 1, inactive: true, accessGrade: 'medium' }],
},
],
cityCount: 1,
officers: Array.from({ length: 8 }, (_, index) => ({
officerLevel: 12 - index,
@@ -62,7 +95,15 @@ const nationDirectory = [
power: 0,
capitalCityId: 0,
generalCount: 1,
totalCrew: 0,
forceEstimate: {
totalGeneralCount: 1,
userCombatGeneralCount: 0,
userTroops: 0,
npcCombatGeneralCount: 1,
npcTroops: 7_000,
inactiveGeneralCount: 0,
},
generalGroups: [],
cityCount: 1,
officers: Array.from({ length: 8 }, (_, index) => ({ officerLevel: 12 - index, general: null })),
ambassadorNames: [],
@@ -351,10 +392,9 @@ test('a level-zero nation shows the ruler current city even without territory th
await expect(table.locator('.roaming-city')).toHaveCSS('color', 'rgb(255, 255, 0)');
});
test('PYA-scale general directory mounts only the active responsive layout and defers portraits', async (
{ page },
testInfo
) => {
test('PYA-scale general directory mounts only the active responsive layout and defers portraits', async ({
page,
}, testInfo) => {
const requestedPortraits = new Set<string>();
page.on('request', (request) => {
if (request.url().includes('/performance/general-')) {
@@ -700,8 +740,16 @@ test('nation directory reuses only the public general-directory row on hover and
await page.waitForLoadState('networkidle');
await expect(page.getByRole('button', { name: '장수 일람 연동' })).toHaveCount(0);
await expect(page.locator('[data-general-preview-trigger]')).toHaveCount(4);
await expect(page.locator('[data-nation-id="1"]')).toContainText('총 병사');
await expect(page.locator('[data-nation-id="1"]')).toContainText('2,500');
const nationAnalysis = page.locator('[data-nation-id="1"] .force-analysis');
await expect(nationAnalysis).not.toContainText('총 병사');
await expect(nationAnalysis).toContainText('무장(1): 조조');
await expect(nationAnalysis).toContainText('지장(1): ⓝ순욱');
await expect(nationAnalysis).toContainText('예상 병력 약 9,000명');
await expect(nationAnalysis).toContainText('삭턴장(1)');
await expect(page.locator('[data-general-preview-trigger="11"]')).toHaveCSS(
'text-decoration-line',
'line-through'
);
expect(requestedOperations.filter((operation) => operation === 'world.getGeneralDirectory')).toHaveLength(
viewport.name === 'desktop' ? 0 : 1
);
@@ -865,9 +913,7 @@ test('nation and general directories rearrange for mobile and keep the tapped pr
await expect(page.locator('.general-card-list [data-general-card-id]')).toHaveCount(2);
await expect(page.locator('[data-directory-tooltip="card-special-domestic-10"]')).toContainText('상재');
await expect(page.locator('[data-directory-tooltip="card-special-war-10"]')).toContainText('귀모');
await expect(page.locator('[data-directory-tooltip="card-injury-leadership-10"] > .wounded')).toHaveText(
'81'
);
await expect(page.locator('[data-directory-tooltip="card-injury-leadership-10"] > .wounded')).toHaveText('81');
await expect(page.locator('[data-general-card-id="10"] .leadership-bonus')).toHaveText('+6');
const mobileInjury = page.locator('[data-directory-tooltip="card-injury-leadership-10"]');
await mobileInjury.focus();
+62 -37
View File
@@ -46,6 +46,11 @@ const officerName = (nation: Nation, officerLevel: number) =>
nation.officers.find((officer) => officer.officerLevel === officerLevel)?.general;
const displayGeneralName = (general: { name: string; npcState: number }) =>
general.npcState > 0 && !/^[ⓜⓝ㉥]/u.test(general.name) ? `${general.name}` : general.name;
const displayAnalyzedGeneralColor = (general: { npcState: number; accessGrade: 'normal' | 'medium' | 'high' }) => {
if (general.accessGrade === 'high') return 'yellow';
if (general.accessGrade === 'medium') return 'lightgreen';
return getNpcColor(general.npcState);
};
const displayAmbassadorName = (nation: Nation, name: string) => {
const general = nation.generals.find((candidate) => candidate.name === name);
return general ? displayGeneralName(general) : name;
@@ -203,16 +208,6 @@ onBeforeUnmount(() => {
<td class="label-cell">장수 / 속령</td>
<td class="value-wide">{{ nation.generalCount }} / {{ nation.cityCount }}</td>
</tr>
<tr class="desktop-only">
<td class="label-cell"> 병사</td>
<td class="value-wide">{{ nation.totalCrew.toLocaleString('ko-KR') }}</td>
<td colspan="6"></td>
</tr>
<tr class="mobile-only">
<td class="label-cell"> 병사</td>
<td class="value-wide">{{ nation.totalCrew.toLocaleString('ko-KR') }}</td>
<td colspan="2"></td>
</tr>
<tr v-for="row in 2" :key="`desktop-officers-${row}`" class="desktop-only">
<template v-for="column in 4" :key="column">
<td class="label-cell">
@@ -287,27 +282,41 @@ onBeforeUnmount(() => {
</td>
</tr>
<tr>
<td colspan="8">
장수 일람 :
<template v-for="general in nation.generals" :key="general.id">
<button
type="button"
class="general-preview-trigger"
:data-general-preview-trigger="general.id"
:style="{ color: getNpcColor(general.npcState) }"
:aria-expanded="activeGeneralId === general.id"
:aria-describedby="
activeGeneralId === general.id ? 'nation-general-preview' : undefined
"
@pointerdown="showGeneralDetailsFromTouch($event, general.id)"
@pointerenter="showGeneralDetails($event, general.id)"
@pointerleave="hideGeneralDetailsFromPointer($event, general.id)"
@focus="showGeneralDetails($event, general.id)"
@blur="hideGeneralDetails(general.id)"
>
{{ displayGeneralName(general) }}</button
>,
</template>
<td colspan="8" class="force-analysis">
<p class="force-summary">
* ({{ nation.forceEstimate.totalGeneralCount }}), 전투장({{
nation.forceEstimate.userCombatGeneralCount
}}, 예상 병력 {{ nation.forceEstimate.userTroops.toLocaleString('ko-KR') }}),
전투N장({{ nation.forceEstimate.npcCombatGeneralCount }}, 예상 병력
{{ nation.forceEstimate.npcTroops.toLocaleString('ko-KR') }}), 삭턴장({{
nation.forceEstimate.inactiveGeneralCount
}}) *
</p>
<p v-for="group in nation.generalGroups" :key="group.type" class="general-group">
<strong class="general-group-label"
>{{ group.label }}({{ group.generals.length }})</strong
>:
<template v-for="(general, index) in group.generals" :key="general.id">
<button
type="button"
class="general-preview-trigger"
:class="{ 'inactive-general': general.inactive }"
:data-general-preview-trigger="general.id"
:style="{ color: displayAnalyzedGeneralColor(general) }"
:aria-expanded="activeGeneralId === general.id"
:aria-describedby="
activeGeneralId === general.id ? 'nation-general-preview' : undefined
"
@pointerdown="showGeneralDetailsFromTouch($event, general.id)"
@pointerenter="showGeneralDetails($event, general.id)"
@pointerleave="hideGeneralDetailsFromPointer($event, general.id)"
@focus="showGeneralDetails($event, general.id)"
@blur="hideGeneralDetails(general.id)"
>
{{ displayGeneralName(general) }}</button
><template v-if="index < group.generals.length - 1">, </template>
</template>
</p>
</td>
</tr>
</tbody>
@@ -326,12 +335,6 @@ onBeforeUnmount(() => {
<td class="neutral-label"> </td>
<td class="neutral-value">{{ nation.cityCount }}</td>
</tr>
<tr>
<td class="neutral-spacer">&nbsp;</td>
<td class="neutral-label"> 병사</td>
<td class="neutral-value">{{ nation.totalCrew.toLocaleString('ko-KR') }}</td>
<td colspan="2"></td>
</tr>
<tr>
<td colspan="5">
속령 일람 :
@@ -505,6 +508,28 @@ onBeforeUnmount(() => {
outline: 1px dashed cyan;
outline-offset: 1px;
}
.force-analysis {
padding: 0 0 0 5.8em !important;
text-indent: -5.8em;
}
.force-summary,
.general-group {
margin: 0;
}
.force-summary {
color: yellow;
font-weight: 700;
text-align: center;
text-indent: 0;
}
.general-group-label {
display: inline-block;
min-width: 5.3em;
text-align: right;
}
.inactive-general {
text-decoration: line-through;
}
.general-hover-preview {
position: fixed;
z-index: 30;