fix: render Ref-compatible GUI display names

This commit is contained in:
2026-08-13 00:00:27 +00:00
parent 6da7d2f899
commit 7ac9035bbc
29 changed files with 834 additions and 261 deletions
@@ -4,8 +4,15 @@ import { asRecord } from '@sammo-ts/common';
import { LogCategory } from '@sammo-ts/logic';
import { accessAuthedProcedure } from '../../../trpc.js';
import {
loadCrewTypeDisplayNames,
loadItemDisplayNames,
resolveDedicationLevelName,
resolveOfficerLevelName,
sanitizeInternalDisplayCode,
} from '../../../services/gameDisplayNames.js';
import { getMyGeneral } from '../../shared/general.js';
import { assertNationAccess, formatDateTime, resolveNationPermission } from '../shared.js';
import { assertNationAccess, formatDateTime, loadTraitNames, resolveNationPermission } from '../shared.js';
export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => {
const me = await getMyGeneral(ctx);
@@ -98,6 +105,36 @@ export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => {
typeof constValues.upgradeLimit === 'number' && Number.isFinite(constValues.upgradeLimit)
? constValues.upgradeLimit
: 30;
const maxDedicationLevel =
typeof constValues.maxDedLevel === 'number' && Number.isFinite(constValues.maxDedLevel)
? Math.max(0, Math.trunc(constValues.maxDedLevel))
: 30;
const [personalityNames, domesticNames, warNames, crewTypeNames, itemNames] = await Promise.all([
loadTraitNames(
generalRows.map((general) => general.personalCode),
'personality'
),
loadTraitNames(
generalRows.map((general) => general.specialCode),
'domestic'
),
loadTraitNames(
generalRows.map((general) => general.special2Code),
'war'
),
loadCrewTypeDisplayNames(worldState, ctx.profile.id),
loadItemDisplayNames(
generalRows.flatMap((general) => [
general.weaponCode,
general.bookCode,
general.horseCode,
general.itemCode,
])
),
]);
const traitName = (code: string, names: Awaited<ReturnType<typeof loadTraitNames>>): string =>
names.get(code)?.name ?? sanitizeInternalDisplayCode(code);
const itemName = (code: string): string => itemNames.get(code) ?? sanitizeInternalDisplayCode(code);
const generals = generalRows.map((general) => {
const meta =
@@ -108,6 +145,11 @@ export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => {
const value = meta[key];
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
};
const storedDedicationLevel = metaNumber('dedlevel');
const dedicationLevel =
storedDedicationLevel > 0
? storedDedicationLevel
: Math.max(0, Math.min(Math.ceil(Math.sqrt(general.dedication) / 10), maxDedicationLevel));
return {
id: general.id,
name: general.name,
@@ -115,6 +157,7 @@ export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => {
imageServer: general.imageServer,
npcState: general.npcState,
officerLevel: general.officerLevel,
officerLevelText: resolveOfficerLevelName(general.officerLevel, nation.level),
cityId: general.cityId,
turnTime: formatDateTime(general.turnTime),
recentWar: formatDateTime(general.recentWarTime),
@@ -134,19 +177,28 @@ export const getBattleCenter = accessAuthedProcedure.query(async ({ ctx }) => {
atmos: general.atmos,
age: general.age,
crewTypeId: general.crewTypeId,
crewTypeName: crewTypeNames.get(general.crewTypeId) ?? '-',
equipment: {
weapon: general.weaponCode,
book: general.bookCode,
horse: general.horseCode,
item: general.itemCode,
},
equipmentNames: {
weapon: itemName(general.weaponCode),
book: itemName(general.bookCode),
horse: itemName(general.horseCode),
item: itemName(general.itemCode),
},
traits: {
personal: general.personalCode,
specialDomestic: general.specialCode,
specialWar: general.special2Code,
personal: traitName(general.personalCode, personalityNames),
specialDomestic: traitName(general.specialCode, domesticNames),
specialWar: traitName(general.special2Code, warNames),
},
progression: {
experienceLevel: metaNumber('explevel'),
dedicationLevel,
dedicationText: resolveDedicationLevelName(dedicationLevel, maxDedicationLevel),
statExperience: {
leadership: metaNumber('leadership_exp'),
strength: metaNumber('strength_exp'),
@@ -1,6 +1,8 @@
import { TRPCError } from '@trpc/server';
import { asNumber, asRecord } from '@sammo-ts/common';
import { accessAuthedProcedure } from '../../../trpc.js';
import { resolveDedicationLevelName, sanitizeInternalDisplayCode } from '../../../services/gameDisplayNames.js';
import { getMyGeneral } from '../../shared/general.js';
import {
assertNationAccess,
@@ -15,8 +17,8 @@ const experienceLevel = (experience: number): number =>
0,
Math.min(100, experience < 1000 ? Math.floor(experience / 100) : Math.floor(Math.sqrt(experience / 10)))
);
const dedicationLevel = (dedication: number): number =>
Math.max(0, Math.min(10, Math.ceil(Math.sqrt(dedication) / 10)));
const dedicationLevel = (dedication: number, maxLevel: number): number =>
Math.max(0, Math.min(maxLevel, Math.ceil(Math.sqrt(dedication) / 10)));
export const getGeneralList = accessAuthedProcedure.query(async ({ ctx }) => {
const general = await getMyGeneral(ctx);
@@ -85,14 +87,22 @@ export const getGeneralList = accessAuthedProcedure.query(async ({ ctx }) => {
const accessByGeneral = new Map(accessRows.map((entry) => [entry.generalId, entry.refreshScoreTotal]));
const nationTrait = (await loadTraitNames([nation.typeCode], 'nation')).get(nation.typeCode);
const permission = resolveNationPermission(general, nation.meta, true);
const config = asRecord(worldState?.config);
const maxDedicationLevel = Math.max(0, Math.trunc(asNumber(asRecord(config.const).maxDedLevel, 30)));
const visibleList = list.map((entry) => {
const entryDedicationLevel = dedicationLevel(entry.dedication, maxDedicationLevel);
const dedicationDisplay = {
dedicationLevel: entryDedicationLevel,
dedicationText: resolveDedicationLevelName(entryDedicationLevel, maxDedicationLevel),
bill: entryDedicationLevel * 200 + 400,
};
const { permission: _targetPermission, ...safeEntry } = entry;
if (permission >= 1) {
return {
...safeEntry,
refreshScoreTotal: accessByGeneral.get(entry.id) ?? 0,
experienceLevel: experienceLevel(entry.experience),
dedicationLevel: dedicationLevel(entry.dedication),
...dedicationDisplay,
};
}
const { crew: _crew, experience: _experience, dedication: _dedication, ...visible } = safeEntry;
@@ -105,7 +115,7 @@ export const getGeneralList = accessAuthedProcedure.query(async ({ ctx }) => {
officerCity: 0,
officerCityName: null,
experienceLevel: experienceLevel(entry.experience),
dedicationLevel: dedicationLevel(entry.dedication),
...dedicationDisplay,
};
});
@@ -118,7 +128,7 @@ export const getGeneralList = accessAuthedProcedure.query(async ({ ctx }) => {
typeCode: nation.typeCode,
type: {
key: nation.typeCode,
name: nationTrait?.name ?? nation.typeCode,
name: nationTrait?.name ?? sanitizeInternalDisplayCode(nation.typeCode),
info: nationTrait?.info ?? '',
},
capitalCityId: nation.capitalCityId ?? 0,
+5 -7
View File
@@ -28,6 +28,7 @@ import {
import type { GameApiContext, InputJsonValue, WorldStateRow } from '../../context.js';
import { purifyNationHtml } from '../../security/nationHtml.js';
import { sanitizeInternalDisplayCode } from '../../services/gameDisplayNames.js';
import { resolveSecretPermission } from '../shared/secretPermission.js';
export type PermissionKind = 'normal' | 'ambassador' | 'auditor';
@@ -313,10 +314,7 @@ export const loadTraitNames = async (keys: Array<string | null>, kind: keyof Tra
}
}
if (eventFiltered.length) {
const modules = await loadEventDomesticTraitModules(
eventFiltered,
new EventDomesticTraitLoader()
);
const modules = await loadEventDomesticTraitModules(eventFiltered, new EventDomesticTraitLoader());
for (const module of modules) {
cache.set(module.key, { name: module.name, info: module.info ?? '' });
}
@@ -513,21 +511,21 @@ export const mapGeneralList = async (
personality: personalityKey
? {
key: personalityKey,
name: personalityMap.get(personalityKey)?.name ?? personalityKey,
name: personalityMap.get(personalityKey)?.name ?? sanitizeInternalDisplayCode(personalityKey),
info: personalityMap.get(personalityKey)?.info ?? '',
}
: null,
specialDomestic: domesticKey
? {
key: domesticKey,
name: domesticMap.get(domesticKey)?.name ?? domesticKey,
name: domesticMap.get(domesticKey)?.name ?? sanitizeInternalDisplayCode(domesticKey),
info: domesticMap.get(domesticKey)?.info ?? '',
}
: null,
specialWar: warKey
? {
key: warKey,
name: warMap.get(warKey)?.name ?? warKey,
name: warMap.get(warKey)?.name ?? sanitizeInternalDisplayCode(warKey),
info: warMap.get(warKey)?.info ?? '',
}
: null,