랜덤 임관 기수의 등용 실행을 차단
This commit is contained in:
@@ -595,15 +595,17 @@ describe('buildTurnCommandTable', () => {
|
|||||||
nationGenerals: null,
|
nationGenerals: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const appointment = table.general
|
for (const commandKey of ['che_임관', 'che_등용']) {
|
||||||
.flatMap((group) => group.values)
|
const command = table.general
|
||||||
.find((command) => command.key === 'che_임관');
|
.flatMap((group) => group.values)
|
||||||
|
.find((candidate) => candidate.key === commandKey);
|
||||||
|
|
||||||
expect(appointment).toMatchObject({
|
expect(command).toMatchObject({
|
||||||
possible: false,
|
possible: false,
|
||||||
status: 'blocked',
|
status: 'blocked',
|
||||||
reason: '랜덤 임관만 가능합니다',
|
reason: '랜덤 임관만 가능합니다',
|
||||||
});
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
it('hides founding at the Ref maxnation boundary without counting Core id=0', async () => {
|
it('hides founding at the Ref maxnation boundary without counting Core id=0', async () => {
|
||||||
|
|||||||
@@ -1418,6 +1418,20 @@ describe('appRouter', () => {
|
|||||||
message: expect.stringContaining('랜덤 임관만 가능합니다'),
|
message: expect.stringContaining('랜덤 임관만 가능합니다'),
|
||||||
});
|
});
|
||||||
expect(randomOnlyWrites).toHaveLength(0);
|
expect(randomOnlyWrites).toHaveLength(0);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
randomOnlyCaller.turns.reserved.setGeneral({
|
||||||
|
generalId: general.id,
|
||||||
|
turnIndex: 0,
|
||||||
|
action: 'che_등용',
|
||||||
|
args: { destGeneralId: 2 },
|
||||||
|
expectedRevision: 0,
|
||||||
|
})
|
||||||
|
).rejects.toMatchObject({
|
||||||
|
code: 'PRECONDITION_FAILED',
|
||||||
|
message: expect.stringContaining('랜덤 임관만 가능합니다'),
|
||||||
|
});
|
||||||
|
expect(randomOnlyWrites).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks nation treaty-term reservation permission but not full diplomacy state', async () => {
|
it('checks nation treaty-term reservation permission but not full diplomacy state', async () => {
|
||||||
|
|||||||
@@ -268,12 +268,13 @@ export const applyLegacyGeneralProgression = (
|
|||||||
const resolveConstraintEnv = (
|
const resolveConstraintEnv = (
|
||||||
world: TurnWorldState,
|
world: TurnWorldState,
|
||||||
scenarioMeta: ScenarioMeta | undefined,
|
scenarioMeta: ScenarioMeta | undefined,
|
||||||
env: TurnCommandEnv
|
env: TurnCommandEnv,
|
||||||
|
worldConfig: Record<string, unknown>
|
||||||
): Record<string, unknown> => {
|
): Record<string, unknown> => {
|
||||||
const worldMeta = asRecord(world.meta);
|
const worldMeta = asRecord(world.meta);
|
||||||
const startYear = typeof scenarioMeta?.startYear === 'number' ? scenarioMeta.startYear : undefined;
|
const startYear = typeof scenarioMeta?.startYear === 'number' ? scenarioMeta.startYear : undefined;
|
||||||
const relYear = typeof startYear === 'number' ? world.currentYear - startYear : undefined;
|
const relYear = typeof startYear === 'number' ? world.currentYear - startYear : undefined;
|
||||||
const joinModeRaw = worldMeta.join_mode ?? worldMeta.joinMode;
|
const joinModeRaw = worldConfig.join_mode ?? worldConfig.joinMode ?? worldMeta.join_mode ?? worldMeta.joinMode;
|
||||||
const joinMode = joinModeRaw === 'onlyRandom' ? 'onlyRandom' : 'full';
|
const joinMode = joinModeRaw === 'onlyRandom' ? 'onlyRandom' : 'full';
|
||||||
const killturnRaw = worldMeta.killturn;
|
const killturnRaw = worldMeta.killturn;
|
||||||
const killturn =
|
const killturn =
|
||||||
@@ -1037,7 +1038,12 @@ export const createReservedTurnHandler = async (options: {
|
|||||||
const worldOverlay = worldRef ? createWorldOverlay(worldRef) : null;
|
const worldOverlay = worldRef ? createWorldOverlay(worldRef) : null;
|
||||||
const worldView = worldOverlay?.view ?? worldRef;
|
const worldView = worldOverlay?.view ?? worldRef;
|
||||||
const baseConstraintEnv = {
|
const baseConstraintEnv = {
|
||||||
...resolveConstraintEnv(context.world, options.scenarioMeta, env),
|
...resolveConstraintEnv(
|
||||||
|
context.world,
|
||||||
|
options.scenarioMeta,
|
||||||
|
env,
|
||||||
|
options.getWorld()?.getWorldConfig() ?? asRecord(options.scenarioConfig)
|
||||||
|
),
|
||||||
...(options.map ? { map: options.map } : {}),
|
...(options.map ? { map: options.map } : {}),
|
||||||
...(options.unitSet ? { unitSet: options.unitSet } : {}),
|
...(options.unitSet ? { unitSet: options.unitSet } : {}),
|
||||||
};
|
};
|
||||||
@@ -2522,7 +2528,7 @@ export const createImmediateGeneralActionExecutor = async (options: {
|
|||||||
|
|
||||||
const state = options.world.getState();
|
const state = options.world.getState();
|
||||||
const constraintEnv = {
|
const constraintEnv = {
|
||||||
...resolveConstraintEnv(state, options.scenarioMeta, env),
|
...resolveConstraintEnv(state, options.scenarioMeta, env, options.world.getWorldConfig()),
|
||||||
...(options.map ? { map: options.map } : {}),
|
...(options.map ? { map: options.map } : {}),
|
||||||
...(options.world.getUnitSet() ? { unitSet: options.world.getUnitSet() } : {}),
|
...(options.world.getUnitSet() ? { unitSet: options.world.getUnitSet() } : {}),
|
||||||
cities: options.world.listCities(),
|
cities: options.world.listCities(),
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ const buildImmediateActionWorld = (options: {
|
|||||||
availableInstantRetreat?: boolean;
|
availableInstantRetreat?: boolean;
|
||||||
lastTurnTime?: Date;
|
lastTurnTime?: Date;
|
||||||
scenarioConst?: Record<string, unknown>;
|
scenarioConst?: Record<string, unknown>;
|
||||||
|
worldConfig?: Record<string, unknown>;
|
||||||
}) => {
|
}) => {
|
||||||
const state: TurnWorldState = {
|
const state: TurnWorldState = {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -209,6 +210,7 @@ const buildImmediateActionWorld = (options: {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
scenarioMeta,
|
scenarioMeta,
|
||||||
|
worldConfig: options.worldConfig,
|
||||||
map: options.map,
|
map: options.map,
|
||||||
};
|
};
|
||||||
const world = new InMemoryTurnWorld(state, snapshot, { schedule });
|
const world = new InMemoryTurnWorld(state, snapshot, { schedule });
|
||||||
@@ -694,6 +696,71 @@ describe('my information world commands', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('rejects recruitment-letter acceptance in a random-appointment-only world', async () => {
|
||||||
|
const recipient = buildGeneral({
|
||||||
|
id: 8,
|
||||||
|
userId: 'user-8',
|
||||||
|
name: '재야장수',
|
||||||
|
nationId: 0,
|
||||||
|
cityId: 1,
|
||||||
|
officerLevel: 0,
|
||||||
|
});
|
||||||
|
const recruiter = buildGeneral({
|
||||||
|
id: 9,
|
||||||
|
userId: 'user-9',
|
||||||
|
name: '등용장수',
|
||||||
|
nationId: 2,
|
||||||
|
cityId: 2,
|
||||||
|
});
|
||||||
|
const map = {
|
||||||
|
id: 'test',
|
||||||
|
name: 'test',
|
||||||
|
cities: [buildMapCity(1, [2]), buildMapCity(2, [1])],
|
||||||
|
};
|
||||||
|
const fixture = buildImmediateActionWorld({
|
||||||
|
general: recipient,
|
||||||
|
additionalGenerals: [recruiter],
|
||||||
|
cities: [
|
||||||
|
{ id: 1, name: '낙양', nationId: 0, supplyState: 1, meta: {} },
|
||||||
|
{ id: 2, name: '장안', nationId: 2, supplyState: 1, meta: {} },
|
||||||
|
] as TurnWorldSnapshot['cities'],
|
||||||
|
nations: [
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: '등용국',
|
||||||
|
color: '#222222',
|
||||||
|
typeCode: 'che_중립',
|
||||||
|
level: 1,
|
||||||
|
capitalCityId: 2,
|
||||||
|
chiefGeneralId: recruiter.id,
|
||||||
|
gold: 0,
|
||||||
|
rice: 0,
|
||||||
|
power: 0,
|
||||||
|
meta: { gennum: 1 },
|
||||||
|
},
|
||||||
|
] as TurnWorldSnapshot['nations'],
|
||||||
|
map,
|
||||||
|
worldConfig: { joinMode: 'onlyRandom' },
|
||||||
|
});
|
||||||
|
const executor = await createImmediateGeneralActionExecutor({
|
||||||
|
world: fixture.world,
|
||||||
|
reservedTurns: fixture.reservedTurns,
|
||||||
|
scenarioMeta: fixture.scenarioMeta,
|
||||||
|
map,
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
executor.execute({
|
||||||
|
actionKey: 'che_등용수락',
|
||||||
|
generalId: recipient.id,
|
||||||
|
rng: new RandUtil(new LiteHashDRBG('reject-random-only-recruitment-letter')),
|
||||||
|
args: { destNationId: 2, destGeneralId: recruiter.id },
|
||||||
|
})
|
||||||
|
).resolves.toEqual({ ok: false, reason: '랜덤 임관만 가능합니다 등용수락 실패.' });
|
||||||
|
expect(fixture.world.getGeneralById(recipient.id)?.nationId).toBe(0);
|
||||||
|
expect(fixture.world.peekDirtyState().logs).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
it('accepts a recruitment letter after the recruiter was deleted and preserves the reserved command', async () => {
|
it('accepts a recruitment letter after the recruiter was deleted and preserves the reserved command', async () => {
|
||||||
const deletedRecruiterId = 99;
|
const deletedRecruiterId = 99;
|
||||||
const originalLastTurn = { command: '내정 특기 초기화', arg: { phase: 2 } };
|
const originalLastTurn = { command: '내정 특기 초기화', arg: { phase: 2 } };
|
||||||
|
|||||||
Reference in New Issue
Block a user