feat: 내정보와 감찰부에 개인 봉급을 표시한다
공헌 등급에서 계산한 개인 봉급을 두 read model에 투영하고 공통 요약 첫 행에 배치한다. 전투·계략·사관 행과 봉급 snapshot이 없는 과거 플레이의 빈 slot은 유지한다.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { TRPCError } from '@trpc/server';
|
import { TRPCError } from '@trpc/server';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { LogCategory, LogScope } from '@sammo-ts/logic';
|
import { getBillByLevel, LogCategory, LogScope } from '@sammo-ts/logic';
|
||||||
import { asRecord, type RankDataType } from '@sammo-ts/common';
|
import { asRecord, type RankDataType } from '@sammo-ts/common';
|
||||||
|
|
||||||
import type { GameApiContext } from '../../context.js';
|
import type { GameApiContext } from '../../context.js';
|
||||||
@@ -517,6 +517,7 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
|
|||||||
injury: general.injury,
|
injury: general.injury,
|
||||||
experience: general.experience,
|
experience: general.experience,
|
||||||
dedication: general.dedication,
|
dedication: general.dedication,
|
||||||
|
bill: getBillByLevel(dedicationLevel),
|
||||||
age: general.age,
|
age: general.age,
|
||||||
retirementYear,
|
retirementYear,
|
||||||
turnTime: general.turnTime.toISOString(),
|
turnTime: general.turnTime.toISOString(),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { TRPCError } from '@trpc/server';
|
import { TRPCError } from '@trpc/server';
|
||||||
|
|
||||||
import { asRecord, type RankDataType } from '@sammo-ts/common';
|
import { asRecord, type RankDataType } from '@sammo-ts/common';
|
||||||
import { LogCategory } from '@sammo-ts/logic';
|
import { getBillByLevel, LogCategory } from '@sammo-ts/logic';
|
||||||
|
|
||||||
import { accessAuthedProcedure } from '../../../trpc.js';
|
import { accessAuthedProcedure } from '../../../trpc.js';
|
||||||
import {
|
import {
|
||||||
@@ -201,6 +201,7 @@ export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => {
|
|||||||
},
|
},
|
||||||
experience: general.experience,
|
experience: general.experience,
|
||||||
dedication: general.dedication,
|
dedication: general.dedication,
|
||||||
|
bill: getBillByLevel(dedicationLevel),
|
||||||
injury: general.injury,
|
injury: general.injury,
|
||||||
gold: general.gold,
|
gold: general.gold,
|
||||||
rice: general.rice,
|
rice: general.rice,
|
||||||
|
|||||||
@@ -297,6 +297,7 @@ describe('in-game my information ownership', () => {
|
|||||||
statUpgradeLimit: 20,
|
statUpgradeLimit: 20,
|
||||||
dex: [350, 1_375, 3_500, 7_125, 12_650],
|
dex: [350, 1_375, 3_500, 7_125, 12_650],
|
||||||
},
|
},
|
||||||
|
bill: 1_000,
|
||||||
},
|
},
|
||||||
city: {
|
city: {
|
||||||
population: 322_886,
|
population: 322_886,
|
||||||
@@ -433,6 +434,7 @@ describe('in-game my information ownership', () => {
|
|||||||
stats: { attack: 100, defence: 150, speed: 7, avoid: 10, magicCoef: 0, cost: 9, rice: 9 },
|
stats: { attack: 100, defence: 150, speed: 7, avoid: 10, magicCoef: 0, cost: 9, rice: 9 },
|
||||||
},
|
},
|
||||||
progression: { experienceLevel: 4, dedicationLevel: 2, dedicationText: '29품관' },
|
progression: { experienceLevel: 4, dedicationLevel: 2, dedicationText: '29품관' },
|
||||||
|
bill: 800,
|
||||||
itemNames: { horse: '노새(+3)' },
|
itemNames: { horse: '노새(+3)' },
|
||||||
itemInfo: { horse: '통솔 +3' },
|
itemInfo: { horse: '통솔 +3' },
|
||||||
},
|
},
|
||||||
@@ -855,6 +857,7 @@ describe('battle-center general and user permissions', () => {
|
|||||||
statUpgradeLimit: 20,
|
statUpgradeLimit: 20,
|
||||||
dex: [0, 0, 0, 0, 0],
|
dex: [0, 0, 0, 0, 0],
|
||||||
},
|
},
|
||||||
|
bill: 600,
|
||||||
serviceYears: 3,
|
serviceYears: 3,
|
||||||
battleStats: {
|
battleStats: {
|
||||||
kills: 5,
|
kills: 5,
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ const myGeneral = (state: FixtureState) => ({
|
|||||||
injury: 0,
|
injury: 0,
|
||||||
experience: 100,
|
experience: 100,
|
||||||
dedication: 200,
|
dedication: 200,
|
||||||
|
bill: 800,
|
||||||
age: 30,
|
age: 30,
|
||||||
turnTime: '2026-01-01 00:10:00',
|
turnTime: '2026-01-01 00:10:00',
|
||||||
recentWar: '2026-01-01 00:00:00',
|
recentWar: '2026-01-01 00:00:00',
|
||||||
@@ -296,6 +297,7 @@ const battleCenter = (state: FixtureState) => ({
|
|||||||
stats: { leadership: 70, strength: 60, intelligence: 50 },
|
stats: { leadership: 70, strength: 60, intelligence: 50 },
|
||||||
experience: 100,
|
experience: 100,
|
||||||
dedication: 200,
|
dedication: 200,
|
||||||
|
bill: 800,
|
||||||
injury: 0,
|
injury: 0,
|
||||||
gold: 1_000,
|
gold: 1_000,
|
||||||
rice: 2_000,
|
rice: 2_000,
|
||||||
@@ -334,6 +336,7 @@ const battleCenter = (state: FixtureState) => ({
|
|||||||
stats: { leadership: 50, strength: 50, intelligence: 50 },
|
stats: { leadership: 50, strength: 50, intelligence: 50 },
|
||||||
experience: 0,
|
experience: 0,
|
||||||
dedication: 0,
|
dedication: 0,
|
||||||
|
bill: 400,
|
||||||
injury: 0,
|
injury: 0,
|
||||||
gold: 500,
|
gold: 500,
|
||||||
rice: 500,
|
rice: 500,
|
||||||
@@ -1245,6 +1248,7 @@ test('내 정보&설정 keeps desktop density and becomes a 390px horizontal-ide
|
|||||||
await expect(page.locator('.general-table')).toContainText('병종보병');
|
await expect(page.locator('.general-table')).toContainText('병종보병');
|
||||||
await expect(page.locator('.general-table')).toContainText('삭턴6 턴');
|
await expect(page.locator('.general-table')).toContainText('삭턴6 턴');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('계급29품관');
|
await expect(page.locator('.battle-general-extra')).toContainText('계급29품관');
|
||||||
|
await expect(page.locator('.battle-general-extra')).toContainText('봉급800');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('전투8회');
|
await expect(page.locator('.battle-general-extra')).toContainText('전투8회');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('계략12');
|
await expect(page.locator('.battle-general-extra')).toContainText('계략12');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('사관4년');
|
await expect(page.locator('.battle-general-extra')).toContainText('사관4년');
|
||||||
@@ -1255,7 +1259,7 @@ test('내 정보&설정 keeps desktop density and becomes a 390px horizontal-ide
|
|||||||
await expect(page.locator('.battle-general-extra__recent-value')).toHaveText('01-01 00:00');
|
await expect(page.locator('.battle-general-extra__recent-value')).toHaveText('01-01 00:00');
|
||||||
await expect(page.locator('.legacy-general-details')).toHaveCount(0);
|
await expect(page.locator('.legacy-general-details')).toHaveCount(0);
|
||||||
expect(await readGeneralSummaryRows(page.locator('.battle-general-extra'))).toEqual([
|
expect(await readGeneralSummaryRows(page.locator('.battle-general-extra'))).toEqual([
|
||||||
['명성', '계급', ''],
|
['명성', '계급', '봉급'],
|
||||||
['전투', '계략', '사관'],
|
['전투', '계략', '사관'],
|
||||||
['승률', '승리', '패배'],
|
['승률', '승리', '패배'],
|
||||||
['살상률', '사살', '피살'],
|
['살상률', '사살', '피살'],
|
||||||
@@ -2039,6 +2043,7 @@ test('감찰부 keeps the selector interaction and shows the permission error pa
|
|||||||
await expect(page.locator('.battle-general-name')).toContainText('간의대부');
|
await expect(page.locator('.battle-general-name')).toContainText('간의대부');
|
||||||
await expect(page.locator('.battle-general-name')).toContainText('건강');
|
await expect(page.locator('.battle-general-name')).toContainText('건강');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('계급29품관');
|
await expect(page.locator('.battle-general-extra')).toContainText('계급29품관');
|
||||||
|
await expect(page.locator('.battle-general-extra')).toContainText('봉급800');
|
||||||
await expect(page.locator('.battle-general-card')).toContainText('병종보병');
|
await expect(page.locator('.battle-general-card')).toContainText('병종보병');
|
||||||
await expect(page.locator('.battle-general-card')).not.toContainText('che_');
|
await expect(page.locator('.battle-general-card')).not.toContainText('che_');
|
||||||
await expect(page.locator('.battle-general-card')).toHaveAttribute('data-general-basic-card', '');
|
await expect(page.locator('.battle-general-card')).toHaveAttribute('data-general-basic-card', '');
|
||||||
@@ -2049,7 +2054,7 @@ test('감찰부 keeps the selector interaction and shows the permission error pa
|
|||||||
await expect(page.locator('.battle-general-extra')).toContainText('승률62.50%');
|
await expect(page.locator('.battle-general-extra')).toContainText('승률62.50%');
|
||||||
await expect(page.locator('.battle-general-extra')).toContainText('살상률181.84%');
|
await expect(page.locator('.battle-general-extra')).toContainText('살상률181.84%');
|
||||||
expect(await readGeneralSummaryRows(page.locator('.battle-general-extra'))).toEqual([
|
expect(await readGeneralSummaryRows(page.locator('.battle-general-extra'))).toEqual([
|
||||||
['명성', '계급', ''],
|
['명성', '계급', '봉급'],
|
||||||
['전투', '계략', '사관'],
|
['전투', '계략', '사관'],
|
||||||
['승률', '승리', '패배'],
|
['승률', '승리', '패배'],
|
||||||
['살상률', '사살', '피살'],
|
['살상률', '사살', '피살'],
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export type GeneralBattleSummaryData = {
|
|||||||
available?: boolean;
|
available?: boolean;
|
||||||
experience?: number | null;
|
experience?: number | null;
|
||||||
dedicationText?: string | null;
|
dedicationText?: string | null;
|
||||||
|
bill?: number | null;
|
||||||
warnum?: number | null;
|
warnum?: number | null;
|
||||||
wins?: number | null;
|
wins?: number | null;
|
||||||
losses?: number | null;
|
losses?: number | null;
|
||||||
@@ -62,8 +63,13 @@ const killRate = computed(() => {
|
|||||||
<template v-else>
|
<template v-else>
|
||||||
<span>명성</span><strong>{{ numberText(summary.experience) }}</strong> <span>계급</span
|
<span>명성</span><strong>{{ numberText(summary.experience) }}</strong> <span>계급</span
|
||||||
><strong>{{ summary.dedicationText || '-' }}</strong>
|
><strong>{{ summary.dedicationText || '-' }}</strong>
|
||||||
<span class="battle-general-extra__empty" aria-hidden="true"></span>
|
<template v-if="summary.bill !== undefined">
|
||||||
<strong class="battle-general-extra__empty" aria-hidden="true"></strong>
|
<span>봉급</span><strong>{{ numberText(summary.bill) }}</strong>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<span class="battle-general-extra__empty" aria-hidden="true"></span>
|
||||||
|
<strong class="battle-general-extra__empty" aria-hidden="true"></strong>
|
||||||
|
</template>
|
||||||
<span>전투</span
|
<span>전투</span
|
||||||
><strong>{{ numberText(summary.warnum) }}<template v-if="summary.warnum != null">회</template></strong>
|
><strong>{{ numberText(summary.warnum) }}<template v-if="summary.warnum != null">회</template></strong>
|
||||||
<span>계략</span><strong>{{ numberText(summary.strategies) }}</strong>
|
<span>계략</span><strong>{{ numberText(summary.strategies) }}</strong>
|
||||||
|
|||||||
@@ -291,6 +291,7 @@ onMounted(() => {
|
|||||||
available: true,
|
available: true,
|
||||||
experience: selectedGeneral.experience,
|
experience: selectedGeneral.experience,
|
||||||
dedicationText: selectedGeneral.progression.dedicationText,
|
dedicationText: selectedGeneral.progression.dedicationText,
|
||||||
|
bill: selectedGeneral.bill,
|
||||||
warnum: selectedGeneral.warnum,
|
warnum: selectedGeneral.warnum,
|
||||||
wins: selectedGeneral.battleStats.kills,
|
wins: selectedGeneral.battleStats.kills,
|
||||||
losses: selectedGeneral.battleStats.deaths,
|
losses: selectedGeneral.battleStats.deaths,
|
||||||
|
|||||||
@@ -421,6 +421,7 @@ onMounted(() => {
|
|||||||
available: true,
|
available: true,
|
||||||
experience: data.general.experience,
|
experience: data.general.experience,
|
||||||
dedicationText: data.general.progression?.dedicationText,
|
dedicationText: data.general.progression?.dedicationText,
|
||||||
|
bill: data.general.bill,
|
||||||
warnum: data.general.records.battles,
|
warnum: data.general.records.battles,
|
||||||
wins: data.general.records.wins,
|
wins: data.general.records.wins,
|
||||||
losses: data.general.records.losses,
|
losses: data.general.records.losses,
|
||||||
|
|||||||
Reference in New Issue
Block a user