feat(game-ui): port Ref progress bars

This commit is contained in:
2026-08-11 00:40:38 +00:00
parent 3b5ae0594f
commit 322a8533fb
18 changed files with 1161 additions and 89 deletions
+19 -1
View File
@@ -249,7 +249,7 @@ export const generalRouter = router({
return null;
}
const [city, nation] = await Promise.all([
const [city, nation, worldState] = await Promise.all([
general.cityId > 0
? ctx.db.city.findUnique({
where: { id: general.cityId },
@@ -259,11 +259,20 @@ export const generalRouter = router({
level: true,
nationId: true,
population: true,
populationMax: true,
agriculture: true,
agricultureMax: true,
commerce: true,
commerceMax: true,
security: true,
securityMax: true,
trust: true,
trade: true,
defence: true,
defenceMax: true,
wall: true,
wallMax: true,
region: true,
supplyState: true,
frontState: true,
},
@@ -285,9 +294,12 @@ export const generalRouter = router({
},
})
: null,
ctx.db.worldState.findFirst({ select: { config: true } }),
]);
const metaRecord = asRecord(general.meta);
const worldConfig = asRecord(worldState?.config);
const constValues = asRecord(worldConfig.const ?? worldConfig.consts);
const settings = resolveUserSettings(metaRecord);
const penalties = resolvePenalty(general.penalty);
@@ -326,6 +338,12 @@ export const generalRouter = router({
progression: {
experienceLevel: readNumber(metaRecord.explevel, 0),
dedicationLevel: readNumber(metaRecord.dedlevel, 0),
statExperience: {
leadership: readNumber(metaRecord.leadership_exp, 0),
strength: readNumber(metaRecord.strength_exp, 0),
intelligence: readNumber(metaRecord.intel_exp, 0),
},
statUpgradeLimit: readNumber(constValues.upgradeLimit, 30),
dex: [1, 2, 3, 4, 5].map((index) => readNumber(metaRecord[`dex${index}`], 0)),
},
items: {
@@ -1,5 +1,6 @@
import { TRPCError } from '@trpc/server';
import { asRecord } from '@sammo-ts/common';
import { LogCategory } from '@sammo-ts/logic';
import { accessAuthedProcedure } from '../../../trpc.js';
@@ -91,6 +92,13 @@ export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => {
}
}
const worldConfig = asRecord(worldState.config);
const constValues = asRecord(worldConfig.const ?? worldConfig.consts);
const statUpgradeLimit =
typeof constValues.upgradeLimit === 'number' && Number.isFinite(constValues.upgradeLimit)
? constValues.upgradeLimit
: 30;
const generals = generalRows.map((general) => {
const meta =
general.meta && typeof general.meta === 'object' && !Array.isArray(general.meta)
@@ -137,6 +145,16 @@ export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => {
specialDomestic: general.specialCode,
specialWar: general.special2Code,
},
progression: {
experienceLevel: metaNumber('explevel'),
statExperience: {
leadership: metaNumber('leadership_exp'),
strength: metaNumber('strength_exp'),
intelligence: metaNumber('intel_exp'),
},
statUpgradeLimit,
dex: [1, 2, 3, 4, 5].map((index) => metaNumber(`dex${index}`)),
},
battleStats: {
kills: metaNumber('rank_killnum') || metaNumber('killnum'),
deaths: metaNumber('deathnum'),
+43 -8
View File
@@ -1,7 +1,7 @@
import { TRPCError } from '@trpc/server';
import { z } from 'zod';
import type { TurnDaemonCommandResult } from '@sammo-ts/common';
import { asRecord, type TurnDaemonCommandResult } from '@sammo-ts/common';
import { isValidTroopNameWidth, normalizeTroopName, resolveTroopSecretPermission } from '@sammo-ts/logic';
import { accessAuthedProcedure, authedProcedure, router } from '../../trpc.js';
@@ -39,7 +39,7 @@ export const troopRouter = router({
throw new TRPCError({ code: 'PRECONDITION_FAILED', message: '국가에 소속되어 있지 않습니다.' });
}
const [nation, troops, generals, cities] = await Promise.all([
const [nation, troops, generals, cities, worldState] = await Promise.all([
ctx.db.nation.findUnique({
where: { id: me.nationId },
select: { id: true, name: true, meta: true },
@@ -58,11 +58,17 @@ export const troopRouter = router({
picture: true,
imageServer: true,
turnTime: true,
leadership: true,
strength: true,
intel: true,
experience: true,
meta: true,
},
}),
ctx.db.city.findMany({
select: { id: true, name: true },
}),
ctx.db.worldState.findFirst({ select: { config: true } }),
]);
if (!nation) {
throw new TRPCError({ code: 'NOT_FOUND', message: '국가 정보를 찾을 수 없습니다.' });
@@ -80,6 +86,12 @@ export const troopRouter = router({
const cityNames = new Map(cities.map((city) => [city.id, city.name]));
const generalMap = new Map(generals.map((general) => [general.id, general]));
const reservedByLeader = new Map<number, string[]>();
const worldConfig = asRecord(worldState?.config);
const constValues = asRecord(worldConfig.const ?? worldConfig.consts);
const statUpgradeLimit =
typeof constValues.upgradeLimit === 'number' && Number.isFinite(constValues.upgradeLimit)
? constValues.upgradeLimit
: 30;
for (const turn of turns) {
const list = reservedByLeader.get(turn.generalId) ?? [];
list.push(turn.actionCode);
@@ -107,12 +119,35 @@ export const troopRouter = router({
: null,
members: generals
.filter((general) => general.troopId === troop.troopLeaderId)
.map((general) => ({
id: general.id,
name: general.name,
cityId: general.cityId,
cityName: cityNames.get(general.cityId) ?? '알 수 없음',
})),
.map((general) => {
const meta = asRecord(general.meta);
const metaNumber = (key: string): number => {
const value = meta[key];
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
};
return {
id: general.id,
name: general.name,
cityId: general.cityId,
cityName: cityNames.get(general.cityId) ?? '알 수 없음',
stats: {
leadership: general.leadership,
strength: general.strength,
intelligence: general.intel,
},
experience: general.experience,
progression: {
experienceLevel: metaNumber('explevel'),
statExperience: {
leadership: metaNumber('leadership_exp'),
strength: metaNumber('strength_exp'),
intelligence: metaNumber('intel_exp'),
},
statUpgradeLimit,
dex: [1, 2, 3, 4, 5].map((index) => metaNumber(`dex${index}`)),
},
};
}),
};
})
.sort((left, right) => {