Refine Xiaopei supply road sortie
This commit is contained in:
@@ -3,10 +3,10 @@ import { existsSync } from 'node:fs';
|
|||||||
import { readFile, writeFile } from 'node:fs/promises';
|
import { readFile, writeFile } from 'node:fs/promises';
|
||||||
import { chromium } from 'playwright';
|
import { chromium } from 'playwright';
|
||||||
|
|
||||||
const targetUrl = process.env.QA_URL ?? 'http://localhost:5173/';
|
const defaultQaPort = process.env.QA_PORT ?? '41737';
|
||||||
|
const targetUrl = process.env.QA_URL ?? `http://127.0.0.1:${defaultQaPort}/`;
|
||||||
const headless = process.env.QA_HEADLESS !== '0';
|
const headless = process.env.QA_HEADLESS !== '0';
|
||||||
const maxRounds = Number(process.env.QA_MAX_ROUNDS ?? 80);
|
const maxRounds = Number(process.env.QA_MAX_ROUNDS ?? 80);
|
||||||
const pnpmCommand = process.env.PNPM_CMD ?? 'pnpm';
|
|
||||||
const cumulativeMode = process.env.QA_CUMULATIVE === '1';
|
const cumulativeMode = process.env.QA_CUMULATIVE === '1';
|
||||||
const campaignStorageKey = 'heros-web:campaign-state';
|
const campaignStorageKey = 'heros-web:campaign-state';
|
||||||
const campaignSlotStorageKey = 'heros-web:campaign-state:slot-1';
|
const campaignSlotStorageKey = 'heros-web:campaign-state:slot-1';
|
||||||
@@ -1478,9 +1478,13 @@ async function ensureLocalServer(url) {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const child = spawn(pnpmCommand, ['dev'], {
|
const viteArgs = localViteServerArgs(url);
|
||||||
|
if (!viteArgs) {
|
||||||
|
throw new Error(`QA target is not reachable and cannot be started locally: ${url}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const child = spawn(process.execPath, viteArgs, {
|
||||||
cwd: process.cwd(),
|
cwd: process.cwd(),
|
||||||
shell: true,
|
|
||||||
stdio: 'ignore',
|
stdio: 'ignore',
|
||||||
windowsHide: true
|
windowsHide: true
|
||||||
});
|
});
|
||||||
@@ -1496,6 +1500,28 @@ async function ensureLocalServer(url) {
|
|||||||
throw new Error(`Could not start dev server at ${url}`);
|
throw new Error(`Could not start dev server at ${url}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function localViteServerArgs(url) {
|
||||||
|
let parsed;
|
||||||
|
try {
|
||||||
|
parsed = new URL(url);
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localHosts = new Set(['localhost', '127.0.0.1', '0.0.0.0', '[::1]', '::1']);
|
||||||
|
if (!localHosts.has(parsed.hostname) || !parsed.port) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const viteCliPath = 'node_modules/vite/bin/vite.js';
|
||||||
|
if (!existsSync(viteCliPath)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const host = parsed.hostname === 'localhost' || parsed.hostname === '0.0.0.0' ? '127.0.0.1' : parsed.hostname;
|
||||||
|
return [viteCliPath, '--host', host, '--port', parsed.port, '--strictPort'];
|
||||||
|
}
|
||||||
|
|
||||||
async function isServerReachable(url) {
|
async function isServerReachable(url) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
|
|||||||
@@ -1091,10 +1091,55 @@ export const eighthBattleScenario: BattleScenarioDefinition = {
|
|||||||
victoryConditionLabel: '고순 격파',
|
victoryConditionLabel: '고순 격파',
|
||||||
defeatConditionLabel: '유비 퇴각',
|
defeatConditionLabel: '유비 퇴각',
|
||||||
openingObjectiveLines: [
|
openingObjectiveLines: [
|
||||||
'여포군의 척후와 습격병이 소패 보급로를 흔들고 있습니다. 고순을 격파하면 서주의 첫 방위선을 지킬 수 있습니다.',
|
'여포군의 척후와 습격병이 소패 보급로의 창고를 노리고 있습니다. 고순을 격파하면 서주의 첫 방위선을 지킬 수 있습니다.',
|
||||||
'기병은 길을 타고 빠르게 파고들고, 궁병은 강 건너와 언덕에서 사거리를 잡습니다. 마을과 창고 지형을 이용해 버티십시오.',
|
'간옹은 병법서와 책략으로 길목 압박을 늦추고, 미축은 창고 주변 보급과 회복을 맡기 좋습니다. 마을과 창고 지형을 중심으로 전열을 잡으십시오.',
|
||||||
'18턴 이내 승리하면 서주 보급망과 새 합류 무장의 명성이 크게 오릅니다.'
|
'18턴 이내 승리하면 서주 보급망과 새 합류 무장의 명성이 크게 오릅니다.'
|
||||||
],
|
],
|
||||||
|
tacticalGuide: {
|
||||||
|
summary: '중앙 창고 마을을 먼저 안정시키고, 관우가 길목을 버티는 동안 간옹과 미축이 후방에서 회복과 책략 보조를 이어갑니다.',
|
||||||
|
route: '아군 진영 → 중앙 창고 마을 → 동쪽 길목 → 고순 본대',
|
||||||
|
focus: '창고 습격병을 먼저 정리해 미축의 보급 위치를 확보하고, 간옹은 화계와 격려로 기병의 접근을 늦추십시오.',
|
||||||
|
roles: ['관우: 보급로 전열', '장비: 남쪽 길 압박', '간옹: 책략/격려 보조', '미축: 창고 보급/회복', '유비: 후방 지휘'],
|
||||||
|
events: {
|
||||||
|
'village-approach': {
|
||||||
|
title: '창고 마을 접근',
|
||||||
|
lines: ['소패 창고 주변에 습격병이 붙어 있습니다.', '미축을 후방에 두고 간옹의 책략으로 길목 적을 약화시키면 보급선을 안정시키기 쉽습니다.']
|
||||||
|
},
|
||||||
|
'enemy-posture': {
|
||||||
|
title: '보급로 압박',
|
||||||
|
lines: ['여포군 기병은 동쪽 길을 타고 빠르게 들어오고, 궁병은 언덕에서 전열을 끊으려 합니다.', '관우는 길목을 잡고 장비는 남쪽 압박, 간옹과 미축은 뒤에서 회복과 책략을 이어가십시오.']
|
||||||
|
},
|
||||||
|
'cavalry-approach': {
|
||||||
|
title: '기병 돌입',
|
||||||
|
lines: ['여포군 기병이 보급로 안쪽으로 파고듭니다.', '장비로 측면을 끊고 간옹의 격려 또는 화계로 돌파 속도를 늦추십시오.']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sortie: {
|
||||||
|
sortieLimit: 5,
|
||||||
|
requiredUnits: ['liu-bei'],
|
||||||
|
recommendedUnits: [
|
||||||
|
{ unitId: 'guan-yu', role: 'front', reason: '보급로 앞 길목을 버티며 고순 본대와 기병 압박을 받아냅니다.' },
|
||||||
|
{ unitId: 'jian-yong', role: 'support', reason: '병법서와 잘 맞는 책략 보조로 화계, 응급, 격려를 모두 활용할 수 있습니다.' },
|
||||||
|
{ unitId: 'mi-zhu', role: 'reserve', reason: '군량 주머니와 회복품 운용으로 창고 주변 장기전을 안정시킵니다.' },
|
||||||
|
{ unitId: 'zhang-fei', role: 'flank', reason: '남쪽 길로 들어오는 기병과 습격병을 빠르게 압박합니다.' },
|
||||||
|
{ unitId: 'liu-bei', role: 'support', reason: '후방에서 생존을 지키며 새 합류 무장과 전열의 균형을 잡습니다.' }
|
||||||
|
],
|
||||||
|
recommendedClasses: [
|
||||||
|
{ label: '보급로 전열', classKeys: ['infantry'], reason: '마을과 길목 방어 보정을 살려 기병과 보병의 첫 압박을 받아냅니다.' },
|
||||||
|
{ label: '책략 보조', classKeys: ['strategist'], reason: '화계와 격려로 원거리 궁병과 기병 돌입을 늦추는 역할이 중요합니다.' },
|
||||||
|
{ label: '창고 보급', classKeys: ['quartermaster'], reason: '회복품과 마을 보정을 묶어 보급로 장기전을 안정시킵니다.' },
|
||||||
|
{ label: '측면 압박', classKeys: ['spearman'], reason: '남쪽 길의 기병 진입과 창고 습격병을 끊어냅니다.' }
|
||||||
|
],
|
||||||
|
deploymentSlots: [
|
||||||
|
{ role: 'front', unitId: 'guan-yu', x: 3, y: 17, label: '보급로 전열' },
|
||||||
|
{ role: 'support', unitId: 'jian-yong', x: 3, y: 18, label: '책략 보조' },
|
||||||
|
{ role: 'reserve', unitId: 'mi-zhu', x: 1, y: 18, label: '창고 보급' },
|
||||||
|
{ role: 'flank', unitId: 'zhang-fei', x: 2, y: 18, label: '남쪽 압박' },
|
||||||
|
{ role: 'support', unitId: 'liu-bei', x: 2, y: 17, label: '후방 지휘' }
|
||||||
|
],
|
||||||
|
note: '소패 보급로는 새로 합류한 간옹과 미축을 시험하기 좋은 전장입니다. 관우가 길목을 막고, 간옹은 책략, 미축은 보급을 맡는 전열이 기본입니다.'
|
||||||
|
},
|
||||||
map: eighthBattleMap,
|
map: eighthBattleMap,
|
||||||
units: eighthBattleUnits,
|
units: eighthBattleUnits,
|
||||||
bonds: eighthBattleBonds,
|
bonds: eighthBattleBonds,
|
||||||
@@ -1122,7 +1167,9 @@ export const eighthBattleScenario: BattleScenarioDefinition = {
|
|||||||
kind: 'secure-terrain',
|
kind: 'secure-terrain',
|
||||||
label: '소패 보급촌 확보',
|
label: '소패 보급촌 확보',
|
||||||
rewardGold: 360,
|
rewardGold: 360,
|
||||||
terrain: 'village'
|
terrain: 'village',
|
||||||
|
targetTile: { x: 12, y: 10, radius: 5 },
|
||||||
|
threatRadius: 0
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'quick',
|
id: 'quick',
|
||||||
@@ -1134,6 +1181,14 @@ export const eighthBattleScenario: BattleScenarioDefinition = {
|
|||||||
],
|
],
|
||||||
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
defeatConditions: [{ kind: 'unit-defeated', unitId: 'liu-bei' }],
|
||||||
itemRewards: ['콩 3', '상처약 2', '탁주 1', '서주 보급망 +1'],
|
itemRewards: ['콩 3', '상처약 2', '탁주 1', '서주 보급망 +1'],
|
||||||
|
campaignReward: {
|
||||||
|
supplies: ['콩 3', '상처약 2', '탁주 1'],
|
||||||
|
equipment: ['군량 주머니 1'],
|
||||||
|
reputation: ['서주 보급망 +1'],
|
||||||
|
unlockBattleId: 'ninth-battle-xuzhou-gate-night-raid',
|
||||||
|
unlockLabel: '서주 성문 야습 개방',
|
||||||
|
note: '소패 보급로를 지켜 미축의 보급 운용과 간옹의 책략 지원이 서주 방위 편성에 자리 잡습니다.'
|
||||||
|
},
|
||||||
victoryPages: eighthBattleVictoryPages,
|
victoryPages: eighthBattleVictoryPages,
|
||||||
nextCampScene: 'CampScene'
|
nextCampScene: 'CampScene'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
|||||||
afterBattleId: seventhBattleScenario.id,
|
afterBattleId: seventhBattleScenario.id,
|
||||||
eyebrow: '다음 전장',
|
eyebrow: '다음 전장',
|
||||||
title: eighthBattleScenario.title,
|
title: eighthBattleScenario.title,
|
||||||
description: '도겸에게 서주를 맡은 뒤 첫 과제는 소패 보급로를 지키는 일입니다. 여포군의 그림자가 드리우기 전에 새로 합류한 장수들과 방위선을 세워야 합니다.',
|
description: '도겸에게 서주를 맡은 뒤 첫 과제는 소패 보급로를 지키는 일입니다. 간옹은 전황 판단을, 미축은 창고 보급을 맡아 새 방위선의 빈틈을 메워야 합니다.',
|
||||||
rewardHint: `예상 보상: ${eighthBattleScenario.title} 개방`,
|
rewardHint: `예상 보상: ${eighthBattleScenario.title} 개방`,
|
||||||
nextBattleId: eighthBattleScenario.id,
|
nextBattleId: eighthBattleScenario.id,
|
||||||
campaignStep: 'eighth-battle',
|
campaignStep: 'eighth-battle',
|
||||||
@@ -292,7 +292,7 @@ const sortieFlows: Record<string, SortieFlow> = {
|
|||||||
afterBattleId: eighthBattleScenario.id,
|
afterBattleId: eighthBattleScenario.id,
|
||||||
eyebrow: '다음 전장',
|
eyebrow: '다음 전장',
|
||||||
title: ninthBattleScenario.title,
|
title: ninthBattleScenario.title,
|
||||||
description: '여포를 의탁시킨 뒤 서주 성문 안팎의 불안이 터집니다. 조표의 야습을 막아 성문을 지켜내야 합니다.',
|
description: '소패 보급로를 지켰지만 서주 성문 안팎의 불안은 곧 터집니다. 보급을 정돈한 미축과 책략 보조를 익힌 간옹을 다음 방위 편성에 넣을지 판단해야 합니다.',
|
||||||
rewardHint: `예상 보상: ${ninthBattleScenario.title} 개방`,
|
rewardHint: `예상 보상: ${ninthBattleScenario.title} 개방`,
|
||||||
nextBattleId: ninthBattleScenario.id,
|
nextBattleId: ninthBattleScenario.id,
|
||||||
campaignStep: 'ninth-battle',
|
campaignStep: 'ninth-battle',
|
||||||
|
|||||||
@@ -1731,14 +1731,14 @@ const enemyAiByUnitId: Record<string, EnemyAiBehavior> = {
|
|||||||
'xuzhou-guard-b': 'guard',
|
'xuzhou-guard-b': 'guard',
|
||||||
'xuzhou-archer-c': 'hold',
|
'xuzhou-archer-c': 'hold',
|
||||||
'xuzhou-leader-xiahou-dun': 'guard',
|
'xuzhou-leader-xiahou-dun': 'guard',
|
||||||
'xiaopei-raider-a': 'aggressive',
|
'xiaopei-raider-a': 'guard',
|
||||||
'xiaopei-raider-b': 'aggressive',
|
'xiaopei-raider-b': 'guard',
|
||||||
'xiaopei-infantry-a': 'guard',
|
'xiaopei-infantry-a': 'guard',
|
||||||
'xiaopei-infantry-b': 'guard',
|
'xiaopei-infantry-b': 'guard',
|
||||||
'xiaopei-archer-a': 'hold',
|
'xiaopei-archer-a': 'hold',
|
||||||
'xiaopei-archer-b': 'hold',
|
'xiaopei-archer-b': 'hold',
|
||||||
'xiaopei-guard-a': 'aggressive',
|
'xiaopei-guard-a': 'guard',
|
||||||
'xiaopei-guard-b': 'aggressive',
|
'xiaopei-guard-b': 'guard',
|
||||||
'xiaopei-cavalry-a': 'aggressive',
|
'xiaopei-cavalry-a': 'aggressive',
|
||||||
'xiaopei-cavalry-b': 'aggressive',
|
'xiaopei-cavalry-b': 'aggressive',
|
||||||
'xiaopei-leader-gao-shun': 'guard',
|
'xiaopei-leader-gao-shun': 'guard',
|
||||||
|
|||||||
@@ -651,11 +651,13 @@ const sortieRulesByBattleId: Partial<Record<BattleScenarioId, SortieRuleDefiniti
|
|||||||
'eighth-battle-xiaopei-supply-road': {
|
'eighth-battle-xiaopei-supply-road': {
|
||||||
maxUnits: 5,
|
maxUnits: 5,
|
||||||
recommended: [
|
recommended: [
|
||||||
{ unitId: 'liu-bei', reason: '서주 방위선의 사기를 붙잡습니다.' },
|
{ unitId: 'guan-yu', reason: '보급로 앞 길목에서 고순 본대와 기병 압박을 받아냅니다.' },
|
||||||
{ unitId: 'jian-yong', reason: '보급로 전투에서 책략 보조가 빛납니다.' },
|
{ unitId: 'jian-yong', reason: '병법서와 책략 보조가 잘 맞아 화계, 응급, 격려를 모두 활용하기 좋습니다.' },
|
||||||
{ unitId: 'mi-zhu', reason: '소패 보급과 회복 운용에 어울립니다.' }
|
{ unitId: 'mi-zhu', reason: '창고 주변 보급과 회복품 운용으로 장기전을 안정시킵니다.' },
|
||||||
|
{ unitId: 'zhang-fei', reason: '남쪽 길의 습격병과 기병을 빠르게 끊어냅니다.' },
|
||||||
|
{ unitId: 'liu-bei', reason: '후방에서 새 합류 무장과 전열의 균형을 잡습니다.' }
|
||||||
],
|
],
|
||||||
note: '새로 합류한 서주 인재를 시험하기 좋은 전장입니다. 전투력, 책략, 보급 추천 사유를 비교해 다섯 명을 채워 보십시오.'
|
note: '새로 합류한 간옹과 미축을 바로 시험하기 좋은 전장입니다. 간옹은 병법서와 책략, 미축은 보급과 회복 역할로 비교해 편성하십시오.'
|
||||||
},
|
},
|
||||||
'ninth-battle-xuzhou-gate-night-raid': {
|
'ninth-battle-xuzhou-gate-night-raid': {
|
||||||
maxUnits: 5,
|
maxUnits: 5,
|
||||||
|
|||||||
Reference in New Issue
Block a user