feat: 사령부 명령 필요 턴을 표시한다

Ref처럼 여러 턴이 필요한 국가 명령의 소요 턴을 명령명 아래에 표시한다. 실행 getPreReqTurn을 기준으로 삼고 천도는 거리 기반 공식을 노출한다.
This commit is contained in:
2026-08-25 04:14:47 +00:00
parent 053f1a3e68
commit 673b8d8a54
7 changed files with 114 additions and 19 deletions
+26 -2
View File
@@ -39,6 +39,7 @@ type AvailabilityStatus = 'available' | 'blocked' | 'needsInput' | 'unknown';
export interface TurnCommandAvailability {
key: string;
name: string;
turnDurationText?: string;
reqArg: boolean;
possible: boolean;
status: AvailabilityStatus;
@@ -699,16 +700,34 @@ const buildEntries = (
return entries;
};
const buildGroups = (entries: CommandEntry[], ctx: ConstraintContext, view: StateView): TurnCommandGroup[] => {
const getTurnDurationText = (definition: GeneralActionDefinition): string | undefined => {
const hint = definition.getTurnDurationHint?.();
if (hint) return hint;
const getPreReqTurn = definition.getPreReqTurn;
// 인자를 선언한 구현은 천도처럼 대상에 따라 달라지므로 임의 context로 실행하지 않는다.
if (!getPreReqTurn || getPreReqTurn.length > 0) return undefined;
const preReqTurn = Math.max(0, Math.floor(getPreReqTurn.call(definition, undefined as never, {})));
return preReqTurn > 0 ? `${preReqTurn + 1}` : undefined;
};
const buildGroups = (
entries: CommandEntry[],
ctx: ConstraintContext,
view: StateView,
includeTurnDuration = false
): TurnCommandGroup[] => {
const groups = new Map<string, TurnCommandAvailability[]>();
for (const entry of entries) {
const availability = entry.evaluate
? entry.evaluate(ctx, view)
: evaluateDefinition(entry.definition, ctx, view, entry.reqArg, entry.availabilityArgs);
const turnDurationText = includeTurnDuration ? getTurnDurationText(entry.definition) : undefined;
const value: TurnCommandAvailability = {
key: entry.definition.key,
name: entry.definition.name,
...(turnDurationText ? { turnDurationText } : {}),
reqArg: entry.reqArg,
inputFields: entry.inputFields,
...availability,
@@ -806,7 +825,12 @@ export const buildTurnCommandTable = async (options: {
ctx,
view
),
nation: buildGroups(projectCommandGroups(nationEntries, nationGroups ?? REF_NATION_COMMAND_GROUPS), ctx, view),
nation: buildGroups(
projectCommandGroups(nationEntries, nationGroups ?? REF_NATION_COMMAND_GROUPS),
ctx,
view,
true
),
inputOptions: options.inputOptions ?? {
cities: [],
nations: [],
+29
View File
@@ -190,6 +190,27 @@ describe('buildTurnCommandTable', () => {
: ['필사즉생', '백성동원', '수몰', '허보', '의병모집', '이호경식', '급습', '피장파장'],
: ['국기변경', '국호변경'],
});
expect(
Object.fromEntries(
table.nation
.flatMap(({ values }) => values)
.filter(({ turnDurationText }) => turnDurationText)
.map(({ key, turnDurationText }) => [key, turnDurationText])
)
).toEqual({
che_초토화: '3턴',
che_천도: '1+거리×2턴',
che_증축: '6턴',
che_감축: '6턴',
che_필사즉생: '3턴',
che_수몰: '3턴',
che_허보: '2턴',
che_의병모집: '3턴',
che_피장파장: '2턴',
});
expect(table.general.flatMap(({ values }) => values)).not.toContainEqual(
expect.objectContaining({ turnDurationText: expect.any(String) })
);
});
it('projects scenario-specific command categories instead of the default profile', async () => {
@@ -225,6 +246,14 @@ describe('buildTurnCommandTable', () => {
'event_대검병연구',
'event_화륜차연구',
]);
expect(
Object.fromEntries(
table.nation.flatMap(({ values }) => values.map(({ key, turnDurationText }) => [key, turnDurationText]))
)
).toMatchObject({
event_대검병연구: '12턴',
event_화륜차연구: '24턴',
});
});
it('projects the real 904/905/910/912 world command profiles into the API table', async () => {