fix(game): 국가 수 판정에서 합성 재야를 제외한다
Ref nation table에 없는 id=0 행이 정찰 차단과 월간 경매 난수 소비에 관여하지 않도록 모집단을 맞춘다. Ref의 maxnation 건국 표시 제한도 실행 제약을 늘리지 않고 명령표에 이관한다.
This commit is contained in:
@@ -349,6 +349,7 @@ export const getTurnCommandTable = async (ctx: GameApiContext, generalId: number
|
||||
city,
|
||||
nation,
|
||||
nationGenerals,
|
||||
realNationCount: nations.filter((entry) => entry.id > 0).length,
|
||||
inputOptions,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -315,6 +315,31 @@ const resolveOptionalString = (source: Record<string, unknown>, keys: string[]):
|
||||
return null;
|
||||
};
|
||||
|
||||
const DEFAULT_MAX_NATION = 55;
|
||||
|
||||
const resolveMaxNation = (worldState: WorldStateRow): number => {
|
||||
const config = asRecord(worldState.config);
|
||||
const constValues = asRecord(config.const);
|
||||
const meta = asRecord(worldState.meta);
|
||||
const refGameEnv = asRecord(meta.refGameEnv);
|
||||
const candidates = [
|
||||
refGameEnv.maxnation,
|
||||
refGameEnv.maxNation,
|
||||
config.maxnation,
|
||||
config.maxNation,
|
||||
constValues.defaultMaxNation,
|
||||
constValues.maxNation,
|
||||
];
|
||||
for (const candidate of candidates) {
|
||||
const parsed =
|
||||
typeof candidate === 'number' ? candidate : typeof candidate === 'string' ? Number(candidate) : Number.NaN;
|
||||
if (Number.isFinite(parsed) && parsed >= 0) {
|
||||
return Math.floor(parsed);
|
||||
}
|
||||
}
|
||||
return DEFAULT_MAX_NATION;
|
||||
};
|
||||
|
||||
const buildCommandEnv = (worldState: WorldStateRow): CommandEnv => {
|
||||
const config = asRecord(worldState.config);
|
||||
const constValues = asRecord(config.const);
|
||||
@@ -630,7 +655,13 @@ const pickAvailability = (lhs: AvailabilityCore, rhs: AvailabilityCore): Availab
|
||||
|
||||
type TurnCommandSpec = GeneralTurnCommandSpec | NationTurnCommandSpec;
|
||||
|
||||
const buildEntries = (env: CommandEnv, specs: TurnCommandSpec[]): CommandEntry[] => {
|
||||
const FOUNDING_COMMAND_KEYS = new Set(['che_건국', 'cr_건국', 'che_무작위건국']);
|
||||
|
||||
const buildEntries = (
|
||||
env: CommandEnv,
|
||||
specs: TurnCommandSpec[],
|
||||
options: { foundingAvailable?: boolean } = {}
|
||||
): CommandEntry[] => {
|
||||
const entries: CommandEntry[] = [];
|
||||
|
||||
for (const spec of specs) {
|
||||
@@ -659,6 +690,14 @@ const buildEntries = (env: CommandEnv, specs: TurnCommandSpec[]): CommandEntry[]
|
||||
};
|
||||
}
|
||||
|
||||
if (FOUNDING_COMMAND_KEYS.has(spec.key) && options.foundingAvailable === false) {
|
||||
entry.evaluate = () => ({
|
||||
possible: false,
|
||||
status: 'blocked',
|
||||
reason: '더 이상 건국은 불가능합니다.',
|
||||
});
|
||||
}
|
||||
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
@@ -740,6 +779,8 @@ export const buildTurnCommandTable = async (options: {
|
||||
city: CityRow | null;
|
||||
nation: NationRow | null;
|
||||
nationGenerals: GeneralRow[] | null;
|
||||
/** Ref's nation-table row count. Core callers must exclude synthetic id=0. */
|
||||
realNationCount?: number;
|
||||
inputOptions?: TurnCommandInputOptions;
|
||||
}): Promise<TurnCommandTable> => {
|
||||
// 턴 입력 화면에서 쓰는 사전 판단이므로 최소 정보로 가능/불가만 계산한다.
|
||||
@@ -761,7 +802,12 @@ export const buildTurnCommandTable = async (options: {
|
||||
|
||||
const env = buildCommandEnv(options.worldState);
|
||||
const { general: generalSpecs, nation: nationSpecs } = await loadTurnCommandSpecs();
|
||||
const generalEntries = buildEntries(env, generalSpecs);
|
||||
const generalEntries = buildEntries(env, generalSpecs, {
|
||||
foundingAvailable:
|
||||
options.realNationCount === undefined
|
||||
? undefined
|
||||
: options.realNationCount < resolveMaxNation(options.worldState),
|
||||
});
|
||||
const nationEntries = buildEntries(env, nationSpecs);
|
||||
|
||||
return {
|
||||
|
||||
@@ -213,9 +213,10 @@ describe('buildTurnCommandTable', () => {
|
||||
for (const field of command.inputFields) {
|
||||
expect(supportedKinds.has(field.kind), `${scope}:${command.key}:${field.key}`).toBe(true);
|
||||
if (field.kind === 'select') {
|
||||
expect(Boolean(field.options?.length || field.optionSource), `${scope}:${command.key}:${field.key}`).toBe(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
Boolean(field.options?.length || field.optionSource),
|
||||
`${scope}:${command.key}:${field.key}`
|
||||
).toBe(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,6 +341,34 @@ describe('buildTurnCommandTable', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('hides founding at the Ref maxnation boundary without counting Core id=0', async () => {
|
||||
const worldState = buildWorldState();
|
||||
worldState.meta = {
|
||||
...((worldState.meta ?? {}) as Record<string, unknown>),
|
||||
refGameEnv: { maxnation: '2' },
|
||||
};
|
||||
const buildAtCount = (realNationCount: number) =>
|
||||
buildTurnCommandTable({
|
||||
worldState,
|
||||
general: buildGeneral(),
|
||||
city: buildCity(),
|
||||
nation: buildNation(),
|
||||
nationGenerals: null,
|
||||
realNationCount,
|
||||
});
|
||||
const findFounding = (table: Awaited<ReturnType<typeof buildAtCount>>) =>
|
||||
table.general.flatMap((group) => group.values).find((command) => command.key === 'che_건국');
|
||||
|
||||
expect(findFounding(await buildAtCount(1))).not.toMatchObject({
|
||||
reason: '더 이상 건국은 불가능합니다.',
|
||||
});
|
||||
expect(findFounding(await buildAtCount(2))).toMatchObject({
|
||||
possible: false,
|
||||
status: 'blocked',
|
||||
reason: '더 이상 건국은 불가능합니다.',
|
||||
});
|
||||
});
|
||||
|
||||
it('projects Ref recruitment availability, combat values, descriptions, and adjusted costs', () => {
|
||||
const general = buildGeneral();
|
||||
general.injury = 3;
|
||||
|
||||
Reference in New Issue
Block a user