최신 main의 수비 설정 수정을 NPC 정책 작업에 통합한다
This commit is contained in:
@@ -2004,8 +2004,10 @@ async function handleSetMySetting(
|
|||||||
const scenarioEffect = world.getScenarioConfig().environment.scenarioEffect;
|
const scenarioEffect = world.getScenarioConfig().environment.scenarioEffect;
|
||||||
const ignoresPenalty = isDefenceTrainPenaltyWaivedByScenarioEffect(scenarioEffect);
|
const ignoresPenalty = isDefenceTrainPenaltyWaivedByScenarioEffect(scenarioEffect);
|
||||||
const constValues = asRecord(world.getScenarioConfig().const);
|
const constValues = asRecord(world.getScenarioConfig().const);
|
||||||
const maxTrain = readMetaNumber(constValues, 'maxTrainByWar', 100);
|
// Ref GameConstBase의 전투 상한을 사용한다. 명령 상한(100)으로 제한하면
|
||||||
const maxAtmos = readMetaNumber(constValues, 'maxAtmosByWar', 100);
|
// 전투에서 오른 훈련·사기가 수비끔 전환 한 번에 소실된다.
|
||||||
|
const maxTrain = readMetaNumber(constValues, 'maxTrainByWar', 110);
|
||||||
|
const maxAtmos = readMetaNumber(constValues, 'maxAtmosByWar', 150);
|
||||||
const trainDelta = ignoresPenalty ? 0 : -3;
|
const trainDelta = ignoresPenalty ? 0 : -3;
|
||||||
const atmosDelta = ignoresPenalty ? 0 : -6;
|
const atmosDelta = ignoresPenalty ? 0 : -6;
|
||||||
nextTrain = Math.max(20, Math.min(maxTrain, general.train + trainDelta));
|
nextTrain = Math.max(20, Math.min(maxTrain, general.train + trainDelta));
|
||||||
|
|||||||
@@ -87,7 +87,11 @@ const buildGeneral = (overrides: Partial<TurnGeneral> = {}): TurnGeneral => ({
|
|||||||
|
|
||||||
const buildWorld = (
|
const buildWorld = (
|
||||||
general = buildGeneral(),
|
general = buildGeneral(),
|
||||||
options: { autorunLimit?: boolean; scenarioEffect?: ScenarioEffectKey | null } = {}
|
options: {
|
||||||
|
autorunLimit?: boolean;
|
||||||
|
scenarioEffect?: ScenarioEffectKey | null;
|
||||||
|
scenarioConst?: Record<string, unknown>;
|
||||||
|
} = {}
|
||||||
) => {
|
) => {
|
||||||
const state: TurnWorldState = {
|
const state: TurnWorldState = {
|
||||||
id: 1,
|
id: 1,
|
||||||
@@ -126,7 +130,7 @@ const buildWorld = (
|
|||||||
stat: { total: 300, min: 10, max: 100, npcTotal: 150, npcMax: 50, npcMin: 10, chiefMin: 65 },
|
stat: { total: 300, min: 10, max: 100, npcTotal: 150, npcMax: 50, npcMin: 10, chiefMin: 65 },
|
||||||
iconPath: '',
|
iconPath: '',
|
||||||
map: {},
|
map: {},
|
||||||
const: { maxTrainByWar: 100, maxAtmosByWar: 100 },
|
const: options.scenarioConst ?? {},
|
||||||
environment: {
|
environment: {
|
||||||
mapName: 'test',
|
mapName: 'test',
|
||||||
unitSet: 'test',
|
unitSet: 'test',
|
||||||
@@ -277,6 +281,53 @@ describe('my information world commands', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{ train: 110, atmos: 150, expectedTrain: 107, expectedAtmos: 144 },
|
||||||
|
{ train: 104, atmos: 107, expectedTrain: 101, expectedAtmos: 101 },
|
||||||
|
{ train: 90, atmos: 90, expectedTrain: 87, expectedAtmos: 84 },
|
||||||
|
{ train: 21, atmos: 25, expectedTrain: 20, expectedAtmos: 20 },
|
||||||
|
{
|
||||||
|
train: 140,
|
||||||
|
atmos: 180,
|
||||||
|
scenarioConst: { maxTrainByWar: 120, maxAtmosByWar: 160 },
|
||||||
|
expectedTrain: 120,
|
||||||
|
expectedAtmos: 160,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
train: 110,
|
||||||
|
atmos: 150,
|
||||||
|
scenarioConst: { maxTrainByWar: 100, maxAtmosByWar: 100 },
|
||||||
|
expectedTrain: 100,
|
||||||
|
expectedAtmos: 100,
|
||||||
|
},
|
||||||
|
])('applies war caps to no-defence penalties: $train/$atmos', async (testCase) => {
|
||||||
|
const fixture = buildWorld(buildGeneral({ train: testCase.train, atmos: testCase.atmos }), {
|
||||||
|
scenarioConst: testCase.scenarioConst,
|
||||||
|
});
|
||||||
|
const command = {
|
||||||
|
type: 'setMySetting' as const,
|
||||||
|
userId: 'user-7',
|
||||||
|
generalId: 7,
|
||||||
|
settings: { defence_train: 999 },
|
||||||
|
};
|
||||||
|
await expect(fixture.handler.handle(command)).resolves.toMatchObject({ ok: true });
|
||||||
|
const expected = {
|
||||||
|
train: testCase.expectedTrain,
|
||||||
|
atmos: testCase.expectedAtmos,
|
||||||
|
meta: { defence_train: 999, myset: 2 },
|
||||||
|
};
|
||||||
|
expect(fixture.world.getGeneralById(7)).toMatchObject(expected);
|
||||||
|
|
||||||
|
// 같은 설정의 재저장과 수비 재개는 훈련·사기를 다시 차감하지 않는다.
|
||||||
|
await fixture.handler.handle(command);
|
||||||
|
expect(fixture.world.getGeneralById(7)).toMatchObject(expected);
|
||||||
|
await fixture.handler.handle({ ...command, settings: { defence_train: 90 } });
|
||||||
|
expect(fixture.world.getGeneralById(7)).toMatchObject({
|
||||||
|
...expected,
|
||||||
|
meta: { defence_train: 90, myset: 1 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
'event_UnlimitedDefenceThresholdChange',
|
'event_UnlimitedDefenceThresholdChange',
|
||||||
'event_StrongAttacker',
|
'event_StrongAttacker',
|
||||||
@@ -284,14 +335,18 @@ describe('my information world commands', () => {
|
|||||||
] satisfies ScenarioEffectKey[])(
|
] satisfies ScenarioEffectKey[])(
|
||||||
'preserves the %s scenario that waives the no-defence penalty',
|
'preserves the %s scenario that waives the no-defence penalty',
|
||||||
async (scenarioEffect) => {
|
async (scenarioEffect) => {
|
||||||
const fixture = buildWorld(buildGeneral(), { scenarioEffect });
|
const fixture = buildWorld(buildGeneral({ train: 110, atmos: 150 }), { scenarioEffect });
|
||||||
await fixture.handler.handle({
|
await fixture.handler.handle({
|
||||||
type: 'setMySetting',
|
type: 'setMySetting',
|
||||||
userId: 'user-7',
|
userId: 'user-7',
|
||||||
generalId: 7,
|
generalId: 7,
|
||||||
settings: { defence_train: 999 },
|
settings: { defence_train: 999 },
|
||||||
});
|
});
|
||||||
expect(fixture.world.getGeneralById(7)).toMatchObject({ train: 90, atmos: 90 });
|
expect(fixture.world.getGeneralById(7)).toMatchObject({
|
||||||
|
train: 110,
|
||||||
|
atmos: 150,
|
||||||
|
meta: { defence_train: 999, myset: 2 },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user