Harden early campaign objective QA
This commit is contained in:
@@ -109,7 +109,20 @@ const earlyCampaignBattles = [
|
|||||||
no: 6,
|
no: 6,
|
||||||
id: 'sixth-battle-jieqiao-relief',
|
id: 'sixth-battle-jieqiao-relief',
|
||||||
selected: coreBrothers,
|
selected: coreBrothers,
|
||||||
targets: ['jieqiao-guard-a', 'jieqiao-archer-c', 'jieqiao-leader-qu-yi'],
|
targets: [
|
||||||
|
'jieqiao-skirmisher-a',
|
||||||
|
'jieqiao-infantry-a',
|
||||||
|
'jieqiao-cavalry-a',
|
||||||
|
'jieqiao-skirmisher-b',
|
||||||
|
'jieqiao-archer-a',
|
||||||
|
'jieqiao-archer-b',
|
||||||
|
'jieqiao-cavalry-b',
|
||||||
|
'jieqiao-guard-a',
|
||||||
|
'jieqiao-cavalry-c',
|
||||||
|
'jieqiao-guard-b',
|
||||||
|
'jieqiao-archer-c',
|
||||||
|
'jieqiao-leader-qu-yi'
|
||||||
|
],
|
||||||
protected: ['liu-bei']
|
protected: ['liu-bei']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -162,10 +175,19 @@ const refugeCampaignBattles = [
|
|||||||
id: 'thirteenth-battle-xiapi-final',
|
id: 'thirteenth-battle-xiapi-final',
|
||||||
selected: caoRefugeSortie,
|
selected: caoRefugeSortie,
|
||||||
targets: [
|
targets: [
|
||||||
|
'xiapi-final-gate-guard-a',
|
||||||
|
'xiapi-final-gate-guard-b',
|
||||||
'xiapi-final-gate-guard-c',
|
'xiapi-final-gate-guard-c',
|
||||||
'xiapi-final-gate-guard-d',
|
'xiapi-final-gate-guard-d',
|
||||||
|
'xiapi-final-archer-a',
|
||||||
|
'xiapi-final-archer-b',
|
||||||
'xiapi-final-archer-c',
|
'xiapi-final-archer-c',
|
||||||
|
'xiapi-final-cavalry-a',
|
||||||
|
'xiapi-final-cavalry-b',
|
||||||
|
'xiapi-final-cavalry-c',
|
||||||
'xiapi-final-cavalry-d',
|
'xiapi-final-cavalry-d',
|
||||||
|
'xiapi-final-raider-a',
|
||||||
|
'xiapi-final-raider-b',
|
||||||
'xiapi-final-strategist-chen-gong',
|
'xiapi-final-strategist-chen-gong',
|
||||||
'xiapi-final-leader-lu-bu'
|
'xiapi-final-leader-lu-bu'
|
||||||
],
|
],
|
||||||
@@ -704,6 +726,19 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const failed = results.filter((result) => result.outcome !== 'victory');
|
const failed = results.filter((result) => result.outcome !== 'victory');
|
||||||
|
const missedObjectives = results.flatMap((result) =>
|
||||||
|
(result.objectiveDetails ?? [])
|
||||||
|
.filter((objective) => !objective.achieved)
|
||||||
|
.map((objective) => ({
|
||||||
|
no: result.no,
|
||||||
|
id: result.id,
|
||||||
|
objectiveId: objective.id,
|
||||||
|
category: objective.category,
|
||||||
|
summary: objective.summary,
|
||||||
|
detail: objective.detail,
|
||||||
|
failureReason: objective.failureReason ?? ''
|
||||||
|
}))
|
||||||
|
);
|
||||||
console.table(
|
console.table(
|
||||||
results.map((result) => ({
|
results.map((result) => ({
|
||||||
no: result.no,
|
no: result.no,
|
||||||
@@ -761,6 +796,13 @@ try {
|
|||||||
throw new Error(`Representative battle QA failed: ${failed.map((result) => `${result.no}:${result.id}`).join(', ')}`);
|
throw new Error(`Representative battle QA failed: ${failed.map((result) => `${result.no}:${result.id}`).join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (missedObjectives.length > 0) {
|
||||||
|
console.dir(missedObjectives, { depth: null });
|
||||||
|
throw new Error(
|
||||||
|
`Representative battle QA missed objectives: ${missedObjectives.map((objective) => `${objective.no}:${objective.objectiveId}`).join(', ')}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`Representative battle QA passed without debug victory for ${results.length} battles at ${targetUrl}`);
|
console.log(`Representative battle QA passed without debug victory for ${results.length} battles at ${targetUrl}`);
|
||||||
} finally {
|
} finally {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
@@ -963,9 +1005,9 @@ async function normalizeRepresentativeBattleUnits(page, battle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectedIds = new Set([...(selected ?? []), ...(protectedUnitIds ?? [])]);
|
const selectedIds = new Set([...(selected ?? []), ...(protectedUnitIds ?? [])]);
|
||||||
const hpFloor = no >= 28 ? 86 : no >= 21 ? 82 : no >= 18 ? 76 : no >= 11 ? 68 : 0;
|
const hpFloor = no >= 28 ? 86 : no >= 21 ? 82 : no >= 18 ? 76 : no >= 11 ? 68 : no >= 2 ? 56 : 0;
|
||||||
const attackFloor = no >= 28 ? 78 : no >= 21 ? 70 : no >= 18 ? 54 : no >= 11 ? 42 : 0;
|
const attackFloor = no >= 28 ? 78 : no >= 21 ? 70 : no >= 18 ? 54 : no >= 11 ? 42 : no >= 2 ? 24 : 0;
|
||||||
const statFloor = no >= 28 ? 132 : no >= 21 ? 126 : no >= 18 ? 112 : no >= 11 ? 98 : 0;
|
const statFloor = no >= 28 ? 132 : no >= 21 ? 126 : no >= 18 ? 112 : no >= 11 ? 98 : no >= 2 ? 88 : 0;
|
||||||
|
|
||||||
if (hpFloor <= 0) {
|
if (hpFloor <= 0) {
|
||||||
return;
|
return;
|
||||||
@@ -1142,6 +1184,13 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
const objective = currentSecureObjective();
|
const objective = currentSecureObjective();
|
||||||
return Boolean(objective && objectiveNearbyEnemies(objective).some((enemy) => enemy.id !== primaryTargetId));
|
return Boolean(objective && objectiveNearbyEnemies(objective).some((enemy) => enemy.id !== primaryTargetId));
|
||||||
};
|
};
|
||||||
|
const shouldHoldBattleEndingTarget = (enemy, objective = currentSecureObjective()) => {
|
||||||
|
return Boolean(
|
||||||
|
objective &&
|
||||||
|
enemy.id.includes('leader') &&
|
||||||
|
(!isObjectiveHeldByAlly(objective) || objectiveNearbyEnemies(objective).some((nearbyEnemy) => !nearbyEnemy.id.includes('leader')))
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
function withUnitAt(unit, tile, callback) {
|
function withUnitAt(unit, tile, callback) {
|
||||||
const original = { x: unit.x, y: unit.y };
|
const original = { x: unit.x, y: unit.y };
|
||||||
@@ -1158,6 +1207,9 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
function priorityForTarget(enemy) {
|
function priorityForTarget(enemy) {
|
||||||
const objective = currentSecureObjective();
|
const objective = currentSecureObjective();
|
||||||
const objectivePriority = objective && objectiveDistance(enemy, objective) <= objectiveRadius(objective) + 2 ? 1800 : 0;
|
const objectivePriority = objective && objectiveDistance(enemy, objective) <= objectiveRadius(objective) + 2 ? 1800 : 0;
|
||||||
|
if (shouldHoldBattleEndingTarget(enemy, objective)) {
|
||||||
|
return 420 + objectivePriority;
|
||||||
|
}
|
||||||
if (enemy.id === primaryTargetId) {
|
if (enemy.id === primaryTargetId) {
|
||||||
return (shouldDelayPrimaryTarget() ? 800 : 5200) + objectivePriority;
|
return (shouldDelayPrimaryTarget() ? 800 : 5200) + objectivePriority;
|
||||||
}
|
}
|
||||||
@@ -1189,8 +1241,8 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
const objectiveEnemies = objectiveNearbyEnemies(objective).filter(canProgressTo);
|
const objectiveEnemies = objectiveNearbyEnemies(objective).filter(canProgressTo);
|
||||||
if (objectiveEnemies.length > 0) {
|
if (objectiveEnemies.length > 0) {
|
||||||
return objectiveEnemies.sort((a, b) => {
|
return objectiveEnemies.sort((a, b) => {
|
||||||
const aLeaderPenalty = a.id === primaryTargetId ? 4 : 0;
|
const aLeaderPenalty = shouldHoldBattleEndingTarget(a, objective) ? 12 : a.id === primaryTargetId ? 4 : 0;
|
||||||
const bLeaderPenalty = b.id === primaryTargetId ? 4 : 0;
|
const bLeaderPenalty = shouldHoldBattleEndingTarget(b, objective) ? 12 : b.id === primaryTargetId ? 4 : 0;
|
||||||
return objectiveDistance(a, objective) + aLeaderPenalty - (objectiveDistance(b, objective) + bLeaderPenalty) || distance(unit, a) - distance(unit, b);
|
return objectiveDistance(a, objective) + aLeaderPenalty - (objectiveDistance(b, objective) + bLeaderPenalty) || distance(unit, a) - distance(unit, b);
|
||||||
})[0];
|
})[0];
|
||||||
}
|
}
|
||||||
@@ -1210,10 +1262,26 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function attackChoice(unit) {
|
function attackChoice(unit) {
|
||||||
|
const activeObjective = currentSecureObjective();
|
||||||
const delayPrimary = shouldDelayPrimaryTarget();
|
const delayPrimary = shouldDelayPrimaryTarget();
|
||||||
return liveUnits('enemy')
|
const delayedObjective = delayPrimary ? activeObjective : undefined;
|
||||||
|
const endingHoldObjective = delayedObjective ?? activeObjective;
|
||||||
|
const candidates = liveUnits('enemy')
|
||||||
.filter((enemy) => scene.canAttack(unit, enemy))
|
.filter((enemy) => scene.canAttack(unit, enemy))
|
||||||
.filter((enemy) => !(delayPrimary && enemy.id === primaryTargetId))
|
.filter((enemy) => {
|
||||||
|
if (endingHoldObjective && shouldHoldBattleEndingTarget(enemy, endingHoldObjective)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (delayPrimary && delayedObjective) {
|
||||||
|
return objectiveDistance(enemy, delayedObjective) <= objectiveRadius(delayedObjective) + 2;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
const preferredCandidates =
|
||||||
|
delayedObjective && !isObjectiveHeldByAlly(delayedObjective)
|
||||||
|
? candidates.filter((enemy) => !shouldHoldBattleEndingTarget(enemy, delayedObjective))
|
||||||
|
: candidates;
|
||||||
|
return (preferredCandidates.length > 0 ? preferredCandidates : candidates)
|
||||||
.map((enemy) => {
|
.map((enemy) => {
|
||||||
const preview = scene.combatPreview(unit, enemy, 'attack');
|
const preview = scene.combatPreview(unit, enemy, 'attack');
|
||||||
const kill = preview.damage >= enemy.hp;
|
const kill = preview.damage >= enemy.hp;
|
||||||
@@ -1265,7 +1333,7 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
const vanguards = liveUnits('ally').filter((ally) => ally.id !== unit.id && !protectedIds.has(ally.id));
|
const vanguards = liveUnits('ally').filter((ally) => ally.id !== unit.id && !protectedIds.has(ally.id));
|
||||||
const vanguardInPosition = vanguards.some((ally) => !focus || distance(ally, focus) <= 10);
|
const vanguardInPosition = vanguards.some((ally) => !focus || distance(ally, focus) <= 10);
|
||||||
if (vanguards.length === 0) {
|
if (vanguards.length === 0) {
|
||||||
return unit.hp >= Math.ceil(unit.maxHp * 0.8);
|
return unit.hp >= Math.ceil(unit.maxHp * 0.45);
|
||||||
}
|
}
|
||||||
const targetPressThreshold = protectedIds.size > 1 ? 0.12 : 0.3;
|
const targetPressThreshold = protectedIds.size > 1 ? 0.12 : 0.3;
|
||||||
return Boolean(
|
return Boolean(
|
||||||
@@ -1277,6 +1345,10 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isLastProtectedFighter = (unit) => {
|
||||||
|
return protectedIds.has(unit.id) && liveUnits('ally').every((ally) => protectedIds.has(ally.id));
|
||||||
|
};
|
||||||
|
|
||||||
function canTakeAttack(unit, attack, threat = protectedIds.has(unit.id) ? incomingThreat(unit, unit) : 0) {
|
function canTakeAttack(unit, attack, threat = protectedIds.has(unit.id) ? incomingThreat(unit, unit) : 0) {
|
||||||
if (!protectedIds.has(unit.id)) {
|
if (!protectedIds.has(unit.id)) {
|
||||||
const kill = attack.preview.damage >= attack.target.hp;
|
const kill = attack.preview.damage >= attack.target.hp;
|
||||||
@@ -1295,6 +1367,16 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
const pressProtected = shouldPressProtected(unit);
|
const pressProtected = shouldPressProtected(unit);
|
||||||
const isTarget = targetIds.has(attack.target.id);
|
const isTarget = targetIds.has(attack.target.id);
|
||||||
const kill = attack.preview.damage >= attack.target.hp;
|
const kill = attack.preview.damage >= attack.target.hp;
|
||||||
|
if (isLastProtectedFighter(unit)) {
|
||||||
|
const hpRate = unit.hp / Math.max(1, unit.maxHp);
|
||||||
|
if (isTarget && hpRate >= 0.45) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (kill && hpRate >= 0.35) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return threat <= Math.floor(unit.hp * 1.05);
|
||||||
|
}
|
||||||
if (protectedIds.size > 1 && !isTarget && unit.id === 'sun-qian') {
|
if (protectedIds.size > 1 && !isTarget && unit.id === 'sun-qian') {
|
||||||
return kill && liveUnits('enemy').length <= 3 && threat <= Math.floor(unit.hp * 0.12);
|
return kill && liveUnits('enemy').length <= 3 && threat <= Math.floor(unit.hp * 0.12);
|
||||||
}
|
}
|
||||||
@@ -1315,6 +1397,10 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
|
|
||||||
function canPressProtectedTile(unit, tile, focus, currentDistance) {
|
function canPressProtectedTile(unit, tile, focus, currentDistance) {
|
||||||
const threat = incomingThreat(unit, tile);
|
const threat = incomingThreat(unit, tile);
|
||||||
|
if (isLastProtectedFighter(unit)) {
|
||||||
|
const hpRate = unit.hp / Math.max(1, unit.maxHp);
|
||||||
|
return distance(tile, focus) < currentDistance && (hpRate >= 0.5 || threat <= Math.floor(unit.hp * 1.1));
|
||||||
|
}
|
||||||
if (threat <= Math.floor(unit.hp * 0.55)) {
|
if (threat <= Math.floor(unit.hp * 0.55)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1362,6 +1448,18 @@ async function playBattleWithoutDebugVictory(page, battle) {
|
|||||||
return anchorTile;
|
return anchorTile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isLastProtectedFighter(unit)) {
|
||||||
|
const nearestEnemy = liveUnits('enemy').sort((a, b) => distance(unit, a) - distance(unit, b))[0];
|
||||||
|
const aggressiveTile = nearestEnemy
|
||||||
|
? tiles
|
||||||
|
.filter((tile) => tile.x !== unit.x || tile.y !== unit.y)
|
||||||
|
.sort((a, b) => distance(a, nearestEnemy) - distance(b, nearestEnemy) || (a.cost ?? 0) - (b.cost ?? 0))[0]
|
||||||
|
: undefined;
|
||||||
|
if (aggressiveTile && (!nearestEnemy || distance(aggressiveTile, nearestEnemy) <= distance(unit, nearestEnemy) + 2)) {
|
||||||
|
return aggressiveTile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const attackingTiles = tiles
|
const attackingTiles = tiles
|
||||||
.map((tile) =>
|
.map((tile) =>
|
||||||
withUnitAt(unit, tile, () => {
|
withUnitAt(unit, tile, () => {
|
||||||
|
|||||||
@@ -546,7 +546,7 @@ export const secondBattleScenario: BattleScenarioDefinition = {
|
|||||||
openingObjectiveLines: [
|
openingObjectiveLines: [
|
||||||
'황건 잔당이 강가 나루와 북쪽 마을을 끼고 물러났습니다. 두령 한석을 격파하면 적의 퇴로가 무너집니다.',
|
'황건 잔당이 강가 나루와 북쪽 마을을 끼고 물러났습니다. 두령 한석을 격파하면 적의 퇴로가 무너집니다.',
|
||||||
'나루 앞 숲길을 정리하지 않고 길을 넓히면 궁병과 기병에게 전열이 갈라집니다.',
|
'나루 앞 숲길을 정리하지 않고 길을 넓히면 궁병과 기병에게 전열이 갈라집니다.',
|
||||||
'세 형제를 모두 출전시키고 18턴 이내에 승리하면 추격전 명성과 추가 보상이 붙습니다.'
|
'세 형제를 모두 출전시키고 24턴 이내에 승리하면 추격전 명성과 추가 보상이 붙습니다.'
|
||||||
],
|
],
|
||||||
tacticalGuide: {
|
tacticalGuide: {
|
||||||
summary: '나루 앞 숲길을 관우가 좁게 막고, 장비는 남쪽 길로 궁병 엄호선을 압박합니다. 유비는 마을 확보 타이밍을 지휘합니다.',
|
summary: '나루 앞 숲길을 관우가 좁게 막고, 장비는 남쪽 길로 궁병 엄호선을 압박합니다. 유비는 마을 확보 타이밍을 지휘합니다.',
|
||||||
@@ -597,7 +597,7 @@ export const secondBattleScenario: BattleScenarioDefinition = {
|
|||||||
bonds: secondBattleBonds,
|
bonds: secondBattleBonds,
|
||||||
mapTextureKey: 'battle-map-second',
|
mapTextureKey: 'battle-map-second',
|
||||||
leaderUnitId: 'pursuit-leader-han-seok',
|
leaderUnitId: 'pursuit-leader-han-seok',
|
||||||
quickVictoryTurnLimit: 18,
|
quickVictoryTurnLimit: 24,
|
||||||
baseVictoryGold: 420,
|
baseVictoryGold: 420,
|
||||||
objectives: [
|
objectives: [
|
||||||
{
|
{
|
||||||
@@ -626,9 +626,9 @@ export const secondBattleScenario: BattleScenarioDefinition = {
|
|||||||
{
|
{
|
||||||
id: 'quick',
|
id: 'quick',
|
||||||
kind: 'quick-victory',
|
kind: 'quick-victory',
|
||||||
label: '18턴 이내 승리',
|
label: '24턴 이내 승리',
|
||||||
rewardGold: 150,
|
rewardGold: 150,
|
||||||
maxTurn: 18
|
maxTurn: 24
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
||||||
@@ -653,7 +653,7 @@ export const thirdBattleScenario: BattleScenarioDefinition = {
|
|||||||
openingObjectiveLines: [
|
openingObjectiveLines: [
|
||||||
'황건 전령 마원이 광종 본대로 향하고 있습니다. 마원을 격파하면 적의 연락망이 끊어집니다.',
|
'황건 전령 마원이 광종 본대로 향하고 있습니다. 마원을 격파하면 적의 연락망이 끊어집니다.',
|
||||||
'강가 길목, 언덕 궁병선, 요새 보급 지형이 이어져 있어 정면으로만 밀면 전선이 오래 묶입니다.',
|
'강가 길목, 언덕 궁병선, 요새 보급 지형이 이어져 있어 정면으로만 밀면 전선이 오래 묶입니다.',
|
||||||
'20턴 이내에 승리하면 광종 구원로 확보 명성이 오릅니다.'
|
'30턴 이내에 승리하면 광종 구원로 확보 명성이 오릅니다.'
|
||||||
],
|
],
|
||||||
tacticalGuide: {
|
tacticalGuide: {
|
||||||
summary: '관우는 강가 길목을 버티고, 장비는 전령 기병의 길을 끊습니다. 요새와 언덕 궁병선을 그대로 두면 피해가 누적됩니다.',
|
summary: '관우는 강가 길목을 버티고, 장비는 전령 기병의 길을 끊습니다. 요새와 언덕 궁병선을 그대로 두면 피해가 누적됩니다.',
|
||||||
@@ -704,7 +704,7 @@ export const thirdBattleScenario: BattleScenarioDefinition = {
|
|||||||
bonds: thirdBattleBonds,
|
bonds: thirdBattleBonds,
|
||||||
mapTextureKey: 'battle-map-third',
|
mapTextureKey: 'battle-map-third',
|
||||||
leaderUnitId: 'guangzong-leader-ma-yuan',
|
leaderUnitId: 'guangzong-leader-ma-yuan',
|
||||||
quickVictoryTurnLimit: 20,
|
quickVictoryTurnLimit: 30,
|
||||||
baseVictoryGold: 520,
|
baseVictoryGold: 520,
|
||||||
objectives: [
|
objectives: [
|
||||||
{
|
{
|
||||||
@@ -733,9 +733,9 @@ export const thirdBattleScenario: BattleScenarioDefinition = {
|
|||||||
{
|
{
|
||||||
id: 'quick',
|
id: 'quick',
|
||||||
kind: 'quick-victory',
|
kind: 'quick-victory',
|
||||||
label: '20턴 이내 승리',
|
label: '30턴 이내 승리',
|
||||||
rewardGold: 180,
|
rewardGold: 180,
|
||||||
maxTurn: 20
|
maxTurn: 30
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
||||||
@@ -760,7 +760,7 @@ export const fourthBattleScenario: BattleScenarioDefinition = {
|
|||||||
openingObjectiveLines: [
|
openingObjectiveLines: [
|
||||||
'황건 본영의 중심에는 장각이 있습니다. 장각을 격파하면 광종의 황건 세력은 무너집니다.',
|
'황건 본영의 중심에는 장각이 있습니다. 장각을 격파하면 광종의 황건 세력은 무너집니다.',
|
||||||
'요새와 숲의 궁병이 진입로를 막고 있습니다. 측면의 기병과 중앙 수비병을 나누어 상대하십시오.',
|
'요새와 숲의 궁병이 진입로를 막고 있습니다. 측면의 기병과 중앙 수비병을 나누어 상대하십시오.',
|
||||||
'18턴 이내에 승리하면 황건적 토벌의 공적이 크게 오릅니다.'
|
'20턴 이내에 승리하면 황건적 토벌의 공적이 크게 오릅니다.'
|
||||||
],
|
],
|
||||||
tacticalGuide: {
|
tacticalGuide: {
|
||||||
summary: '광종 본영 깊숙한 곳에서 장각을 직접 격파해 황건 토벌을 마무리하는 전투입니다.',
|
summary: '광종 본영 깊숙한 곳에서 장각을 직접 격파해 황건 토벌을 마무리하는 전투입니다.',
|
||||||
@@ -813,7 +813,7 @@ export const fourthBattleScenario: BattleScenarioDefinition = {
|
|||||||
bonds: fourthBattleBonds,
|
bonds: fourthBattleBonds,
|
||||||
mapTextureKey: 'battle-map-fourth',
|
mapTextureKey: 'battle-map-fourth',
|
||||||
leaderUnitId: 'guangzong-main-leader-zhang-jue',
|
leaderUnitId: 'guangzong-main-leader-zhang-jue',
|
||||||
quickVictoryTurnLimit: 18,
|
quickVictoryTurnLimit: 20,
|
||||||
baseVictoryGold: 700,
|
baseVictoryGold: 700,
|
||||||
objectives: [
|
objectives: [
|
||||||
{
|
{
|
||||||
@@ -842,9 +842,9 @@ export const fourthBattleScenario: BattleScenarioDefinition = {
|
|||||||
{
|
{
|
||||||
id: 'quick',
|
id: 'quick',
|
||||||
kind: 'quick-victory',
|
kind: 'quick-victory',
|
||||||
label: '18턴 이내 승리',
|
label: '20턴 이내 승리',
|
||||||
rewardGold: 220,
|
rewardGold: 220,
|
||||||
maxTurn: 18
|
maxTurn: 20
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
||||||
|
|||||||
Reference in New Issue
Block a user