Gateway 화면을 route 단위로 지연 로딩하고 common runtime import를 실제 소유 leaf export로 분리한다.\n\npackage boundary와 route loading 회귀 테스트로 root barrel 재유입을 차단한다.
189 lines
8.3 KiB
TypeScript
189 lines
8.3 KiB
TypeScript
import { JosaUtil } from '@sammo-ts/common/util/JosaUtil';
|
|
|
|
import type { CommandOption, CommandTable } from './types';
|
|
|
|
type CommandScope = 'general' | 'nation';
|
|
type CommandArgs = Record<string, unknown>;
|
|
|
|
const CITY_TO_COMMANDS = new Set(['che_이동', 'che_강행', 'che_출병', 'che_천도']);
|
|
const CITY_AT_COMMANDS = new Set([
|
|
'che_첩보',
|
|
'che_화계',
|
|
'che_선동',
|
|
'che_탈취',
|
|
'che_파괴',
|
|
'che_백성동원',
|
|
'che_허보',
|
|
]);
|
|
const CITY_OBJECT_COMMANDS = new Set(['che_수몰', 'che_초토화']);
|
|
const NATION_AT_COMMANDS = new Set(['che_선전포고', 'che_급습', 'che_이호경식']);
|
|
const NATION_PROPOSAL_COMMANDS = new Set(['che_불가침파기제의', 'che_종전제의']);
|
|
|
|
const ITEM_TYPE_NAMES: Record<string, string> = {
|
|
horse: '명마',
|
|
weapon: '무기',
|
|
book: '서적',
|
|
item: '도구',
|
|
};
|
|
|
|
const asArgs = (value: unknown): CommandArgs =>
|
|
value && typeof value === 'object' && !Array.isArray(value) ? (value as CommandArgs) : {};
|
|
|
|
const firstValue = (args: CommandArgs, keys: readonly string[]): unknown => {
|
|
for (const key of keys) {
|
|
if (args[key] !== undefined) return args[key];
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const optionLabel = (options: readonly CommandOption[], value: unknown): string | null => {
|
|
const option = options.find((entry) => entry.value === value || String(entry.value) === String(value));
|
|
if (!option) return null;
|
|
// 도시·장수 option에는 선택 보조용 소속 정보가 괄호로 붙지만 Ref brief에는 이름만 들어간다.
|
|
return option.label.replace(/\s+\([^)]*\)$/u, '');
|
|
};
|
|
|
|
const commandNameMap = (table: CommandTable | null, scope: CommandScope): Map<string, string> => {
|
|
const result = new Map<string, string>([['휴식', '휴식']]);
|
|
for (const group of table?.[scope] ?? []) {
|
|
for (const command of group.values) result.set(command.key, command.name);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
const defaultCommandName = (action: string): string => action.replace(/^(?:che_|cr_|event_)/u, '');
|
|
|
|
const numberText = (value: unknown, grouped = false): string => {
|
|
if (typeof value !== 'number' || !Number.isFinite(value)) return String(value ?? '');
|
|
return grouped ? value.toLocaleString('en-US') : String(value);
|
|
};
|
|
|
|
const wrap = (value: string): string => `【${value}】`;
|
|
const withParticle = (value: string, particle: '을' | '으로'): string => `${value}${JosaUtil.pick(value, particle)}`;
|
|
const wrappedWithParticle = (value: string, particle: '을' | '으로'): string =>
|
|
`${wrap(value)}${JosaUtil.pick(value, particle)}`;
|
|
|
|
export const formatReservedCommandBrief = (
|
|
scope: CommandScope,
|
|
action: string,
|
|
rawArgs: unknown,
|
|
table: CommandTable | null
|
|
): string => {
|
|
const args = asArgs(rawArgs);
|
|
const input = table?.inputOptions;
|
|
const commandNames = commandNameMap(table, scope);
|
|
const commandName = commandNames.get(action) ?? defaultCommandName(action);
|
|
const cityName = optionLabel(input?.cities ?? [], firstValue(args, ['destCityId', 'destCityID']));
|
|
const nationName = optionLabel(input?.nations ?? [], firstValue(args, ['destNationId', 'destNationID']));
|
|
const generalName = optionLabel(
|
|
input?.generalTargets?.[action] ?? input?.generals ?? [],
|
|
firstValue(args, ['destGeneralId', 'destGeneralID'])
|
|
);
|
|
|
|
if (CITY_TO_COMMANDS.has(action) && cityName) {
|
|
return `${wrappedWithParticle(cityName, '으로')} ${commandName}`;
|
|
}
|
|
if (action === 'cr_인구이동' && cityName) {
|
|
return `${wrappedWithParticle(cityName, '으로')} ${numberText(args.amount, true)}명 ${commandName}`;
|
|
}
|
|
if (CITY_AT_COMMANDS.has(action) && cityName) {
|
|
const suffix =
|
|
scope === 'nation' ? commandName : action === 'che_첩보' ? `${commandName} 실행` : `${commandName}실행`;
|
|
return `${wrap(cityName)}에 ${suffix}`;
|
|
}
|
|
if (CITY_OBJECT_COMMANDS.has(action) && cityName) {
|
|
return `${wrappedWithParticle(cityName, '을')} ${commandName}`;
|
|
}
|
|
if (action === 'che_임관' && nationName) {
|
|
return `${wrappedWithParticle(nationName, '으로')} ${commandName}`;
|
|
}
|
|
if (NATION_AT_COMMANDS.has(action) && nationName) {
|
|
return `${wrap(nationName)}에 ${commandName}`;
|
|
}
|
|
if (NATION_PROPOSAL_COMMANDS.has(action) && nationName) {
|
|
return `${wrap(nationName)}에게 ${commandName}`;
|
|
}
|
|
if (action === 'che_불가침제의' && nationName) {
|
|
return `${wrap(nationName)}에게 ${numberText(args.year)}년 ${numberText(args.month)}월까지 ${commandName}`;
|
|
}
|
|
if (action === 'che_등용' && generalName) {
|
|
return `${wrappedWithParticle(generalName, '을')} ${commandName}`;
|
|
}
|
|
if (action === 'che_장수대상임관' && generalName) {
|
|
return `${wrappedWithParticle(generalName, '을')} 따라 임관`;
|
|
}
|
|
if (action === 'che_선양' && generalName) {
|
|
return `${wrap(generalName)}에게 ${commandName}`;
|
|
}
|
|
if (action === 'che_랜덤임관') {
|
|
return '무작위 국가로 임관';
|
|
}
|
|
if ((action === 'che_건국' || action === 'cr_건국') && typeof args.nationName === 'string') {
|
|
return `${wrappedWithParticle(args.nationName, '을')} 건국`;
|
|
}
|
|
if (action === 'che_무작위건국' && typeof args.nationName === 'string') {
|
|
return `${wrappedWithParticle(args.nationName, '을')} 무작위 도시에 건국`;
|
|
}
|
|
if (action === 'che_군량매매') {
|
|
return `군량 ${numberText(args.amount)}을 ${args.buyRice ? '구입' : '판매'}`;
|
|
}
|
|
if (action === 'che_헌납') {
|
|
return `${args.isGold ? '금' : '쌀'} ${numberText(args.amount)}을 ${commandName}`;
|
|
}
|
|
if (action === 'che_증여' && generalName) {
|
|
return `${wrap(generalName)}에게 ${args.isGold ? '금' : '쌀'} ${numberText(args.amount)}을 ${commandName}`;
|
|
}
|
|
if (action === 'che_징병' || action === 'che_모병') {
|
|
const crewType = optionLabel(input?.crewTypes ?? [], args.crewType);
|
|
if (crewType) return `${wrap(crewType)} ${numberText(args.amount)}명 ${commandName}`;
|
|
}
|
|
if (action === 'che_숙련전환') {
|
|
const srcArmType = optionLabel(input?.armTypes ?? [], args.srcArmType);
|
|
const destArmType = optionLabel(input?.armTypes ?? [], args.destArmType);
|
|
if (srcArmType && destArmType) return `${wrap(srcArmType)}숙련을 ${wrap(destArmType)}숙련으로 전환`;
|
|
}
|
|
if (action === 'che_장비매매') {
|
|
const itemType = typeof args.itemType === 'string' ? args.itemType : '';
|
|
if (args.itemCode === 'None') {
|
|
const itemTypeName = ITEM_TYPE_NAMES[itemType];
|
|
if (itemTypeName) return `${withParticle(itemTypeName, '을')} 판매`;
|
|
}
|
|
const itemName = optionLabel(input?.items[itemType] ?? [], args.itemCode);
|
|
if (itemName) {
|
|
const itemRawName = itemName.replace(/\([^)]*\)$/u, '');
|
|
return `${wrap(itemName)}${JosaUtil.pick(itemRawName, '을')} 구입`;
|
|
}
|
|
}
|
|
if (action === 'che_발령' && generalName && cityName) {
|
|
return `${wrap(generalName)}${wrappedWithParticle(cityName, '으로')} ${commandName}`;
|
|
}
|
|
if (action === 'che_부대탈퇴지시' && generalName) {
|
|
return `${wrap(generalName)}${commandName}`;
|
|
}
|
|
if ((action === 'che_포상' || action === 'che_몰수') && generalName) {
|
|
return `${wrap(generalName)} ${args.isGold ? '금' : '쌀'} ${numberText(args.amount, true)} ${commandName}`;
|
|
}
|
|
if (action === 'che_물자원조' && nationName && Array.isArray(args.amountList)) {
|
|
return `${wrap(nationName)}에게 국고 ${numberText(args.amountList[0], true)} 병량 ${numberText(
|
|
args.amountList[1],
|
|
true
|
|
)} ${commandName}`;
|
|
}
|
|
if (action === 'che_증축' || action === 'che_감축') {
|
|
return `수도를 ${commandName}`;
|
|
}
|
|
if (action === 'che_국기변경') {
|
|
return '【국기】를 변경';
|
|
}
|
|
if (action === 'che_국호변경' && typeof args.nationName === 'string') {
|
|
return `국호를 ${wrappedWithParticle(args.nationName, '으로')} 변경`;
|
|
}
|
|
if (action === 'che_피장파장' && nationName) {
|
|
const targetAction = typeof args.commandType === 'string' ? args.commandType : '';
|
|
const targetName = commandNames.get(targetAction) ?? defaultCommandName(targetAction);
|
|
return `${wrap(nationName)}에 ${wrap(targetName)} ${commandName}`;
|
|
}
|
|
|
|
return commandName;
|
|
};
|