merge: 최신 main 변경을 유산 전투 특기 수정에 통합

This commit is contained in:
2026-08-21 04:45:43 +00:00
12 changed files with 302 additions and 40 deletions
+25 -2
View File
@@ -2,7 +2,7 @@ import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import { LogCategory, LogScope } from '@sammo-ts/logic';
import { asRecord } from '@sammo-ts/common';
import { asRecord, type RankDataType } from '@sammo-ts/common';
import type { GameApiContext } from '../../context.js';
import {
@@ -63,6 +63,14 @@ const zImmediateActionInput = z
})
.optional();
const MAIN_RECORD_LIMIT = 15;
const PERSONAL_RECORD_TYPES = [
'firenum',
'warnum',
'killnum',
'deathnum',
'killcrew',
'deathcrew',
] as const satisfies readonly RankDataType[];
const NEUTRAL_NATION_CONTEXT = {
id: 0,
name: '재야',
@@ -276,7 +284,7 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
const metaRecord = asRecord(general.meta);
const officerCityId = readNumber(metaRecord.officerCity ?? metaRecord.officer_city ?? metaRecord.officerCityId, 0);
const [city, queriedNation, worldState, officerCity, troop, troopLeader, troopLeaderFirstTurn, accessLog] =
const [city, queriedNation, worldState, officerCity, troop, troopLeader, troopLeaderFirstTurn, accessLog, rankRows] =
await Promise.all([
general.cityId > 0
? ctx.db.city.findUnique({
@@ -346,6 +354,10 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
where: { generalId: general.id },
select: { refreshScore: true, refreshScoreTotal: true },
}),
ctx.db.rankData.findMany({
where: { generalId: general.id, type: { in: [...PERSONAL_RECORD_TYPES] } },
select: { type: true, value: true },
}),
]);
const nation = queriedNation ?? NEUTRAL_NATION_CONTEXT;
@@ -466,6 +478,8 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
};
const refreshScore = accessLog?.refreshScore ?? 0;
const refreshScoreTotal = accessLog?.refreshScoreTotal ?? 0;
const rankValues = new Map(rankRows.map((row) => [row.type, row.value]));
const rankValue = (type: (typeof PERSONAL_RECORD_TYPES)[number]): number => rankValues.get(type) ?? 0;
const troopStatus: 'inactive' | 'present' | 'away' =
troopLeaderFirstTurn?.actionCode !== undefined && troopLeaderFirstTurn.actionCode !== 'che_집합'
? 'inactive'
@@ -527,6 +541,15 @@ export const getGeneralContext = async (ctx: GameApiContext) => {
statUpgradeLimit: readNumber(constValues.upgradeLimit, 30),
dex: [1, 2, 3, 4, 5].map((index) => readNumber(metaRecord[`dex${index}`], 0)),
},
records: {
battles: rankValue('warnum'),
strategies: rankValue('firenum'),
serviceYears: readNumber(metaRecord.belong, 0),
wins: rankValue('killnum'),
losses: rankValue('deathnum'),
killedCrew: rankValue('killcrew'),
lostCrew: rankValue('deathcrew'),
},
items: {
horse: normalizeItemCode(general.horseCode),
weapon: normalizeItemCode(general.weaponCode),
@@ -83,6 +83,7 @@ const buildContext = (authenticated: boolean, generalAccessTracking = false) =>
general: {
findFirst: findGeneral,
},
rankData: { findMany: async () => [] },
city: { findUnique: findCity },
nation: { findUnique: findNation },
generalAccessLog: { findUnique: async () => null },
@@ -85,6 +85,7 @@ const createContext = (options: {
troopLeaderAction?: string | null;
refreshScore?: number;
refreshScoreTotal?: number;
rankRows?: Array<{ type: string; value: number }>;
requestId?: string;
transaction?: ReturnType<typeof vi.fn>;
}) => {
@@ -128,6 +129,9 @@ const createContext = (options: {
refreshScoreTotal: options.refreshScoreTotal ?? 0,
})),
},
rankData: {
findMany: vi.fn(async () => options.rankRows ?? []),
},
city: {
findUnique: vi.fn(async () => options.city ?? null),
aggregate: vi.fn(async () => ({
@@ -206,6 +210,41 @@ const createContext = (options: {
};
describe('in-game my information ownership', () => {
it('returns the owned general battle records from the same rank_data source used by rankings', async () => {
const fixture = createContext({
me: buildGeneral({ meta: { belong: 4, rank_killnum: 999 } }),
rankRows: [
{ type: 'firenum', value: 12 },
{ type: 'warnum', value: 8 },
{ type: 'killnum', value: 5 },
{ type: 'deathnum', value: 3 },
{ type: 'killcrew', value: 12_345 },
{ type: 'deathcrew', value: 6_789 },
],
});
await expect(appRouter.createCaller(fixture.context).general.me()).resolves.toMatchObject({
general: {
records: {
battles: 8,
strategies: 12,
serviceYears: 4,
wins: 5,
losses: 3,
killedCrew: 12_345,
lostCrew: 6_789,
},
},
});
expect(fixture.db.rankData.findMany).toHaveBeenCalledWith({
where: {
generalId: 7,
type: { in: ['firenum', 'warnum', 'killnum', 'deathnum', 'killcrew', 'deathcrew'] },
},
select: { type: true, value: true },
});
});
it('returns every ref progress-bar input from the owned general and current city read model', async () => {
const fixture = createContext({
me: buildGeneral({