|
|
|
|
@@ -191,6 +191,7 @@ type SortieRecommendation = {
|
|
|
|
|
type SortieRuleDefinition = {
|
|
|
|
|
maxUnits: number;
|
|
|
|
|
requiredUnitIds?: string[];
|
|
|
|
|
excludedUnitIds?: string[];
|
|
|
|
|
recommended: SortieRecommendation[];
|
|
|
|
|
note: string;
|
|
|
|
|
};
|
|
|
|
|
@@ -424,6 +425,14 @@ const campaignTimelineChapters: CampaignTimelineChapter[] = [
|
|
|
|
|
'fifty-fourth-battle-meng-huo-final-capture'
|
|
|
|
|
],
|
|
|
|
|
nextHints: ['왕업 선언', '번성 포위', '형주 위기', '이릉 화공', '남중 안정', '맹획 회유', '칠종칠금']
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'northern-campaign',
|
|
|
|
|
title: '북벌 준비',
|
|
|
|
|
period: '출사표를 향해',
|
|
|
|
|
description: '남중 평정으로 뒤를 안정시킨 뒤, 한중 창고와 출전 무장 편성을 정비해 첫 북벌을 준비하는 장입니다.',
|
|
|
|
|
battleIds: [],
|
|
|
|
|
nextHints: ['한중 창고 정비', '출전 무장 재편', '기산 출진로', '장기전 보급']
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
@@ -1143,6 +1152,24 @@ const sortieRulesByBattleId: Partial<Record<BattleScenarioId, SortieRuleDefiniti
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const sortieRulesByCampaignStep: Partial<Record<CampaignStep, SortieRuleDefinition>> = {
|
|
|
|
|
'northern-campaign-prep-camp': {
|
|
|
|
|
maxUnits: 7,
|
|
|
|
|
requiredUnitIds: ['zhuge-liang'],
|
|
|
|
|
excludedUnitIds: ['liu-bei'],
|
|
|
|
|
recommended: [
|
|
|
|
|
{ unitId: 'zhuge-liang', reason: '북벌 준비 회의의 중심입니다. 한중 창고와 전군 편성을 직접 조율해야 합니다.' },
|
|
|
|
|
{ unitId: 'zhao-yun', reason: '기산 출진 전 조운의 기동대 운용과 우회로 정찰이 필요합니다.' },
|
|
|
|
|
{ unitId: 'huang-quan', reason: '한중과 익주의 군량 장부를 맞춰 장기전 보급선을 안정시킵니다.' },
|
|
|
|
|
{ unitId: 'ma-liang', reason: '문서와 민심을 정리해 북벌 명분과 후방 행정의 빈틈을 줄입니다.' },
|
|
|
|
|
{ unitId: 'wang-ping', reason: '산길과 진영 배치를 읽어 다음 북방 전장의 지형 선택을 돕습니다.' },
|
|
|
|
|
{ unitId: 'ma-dai', reason: '기병 정찰과 추격 차단을 맡아 출진로의 측면 위험을 줄입니다.' },
|
|
|
|
|
{ unitId: 'wei-yan', reason: '거친 돌파력을 어디까지 전방에 둘지 미리 판단해야 할 장수입니다.' }
|
|
|
|
|
],
|
|
|
|
|
note: '북벌 준비 회의는 전투가 아니지만 다음 장기전의 편성을 정하는 단계입니다. 제갈량을 중심으로 보급, 산길, 기동, 돌파 역할을 균형 있게 고르십시오.'
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const campDialogues: CampDialogue[] = [
|
|
|
|
|
{
|
|
|
|
|
id: 'liu-guan-after-first-battle',
|
|
|
|
|
@@ -5682,6 +5709,90 @@ const campDialogues: CampDialogue[] = [
|
|
|
|
|
rewardExp: 22
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'northern-prep-zhuge-zhao-march',
|
|
|
|
|
title: '북쪽으로 놓는 첫 발',
|
|
|
|
|
availableAfterBattleIds: [campBattleIds.fiftyFourth],
|
|
|
|
|
availableDuringSteps: ['northern-campaign-prep-camp'],
|
|
|
|
|
unitIds: ['zhuge-liang', 'zhao-yun'],
|
|
|
|
|
bondId: 'zhao-yun__zhuge-liang',
|
|
|
|
|
rewardExp: 76,
|
|
|
|
|
lines: [
|
|
|
|
|
'조운: 남중의 길은 정리되었지만, 북쪽 산길은 적이 먼저 기다리는 길입니다.',
|
|
|
|
|
'제갈량: 그래서 빠른 창보다 먼저 돌아올 수 있는 길을 정해야 합니다. 자룡의 기동대는 승리를 넓히는 손이 아니라, 전군을 잃지 않게 묶는 끈이 되어야 합니다.',
|
|
|
|
|
'조운: 알겠습니다. 다음 출진에서는 먼저 달리는 것보다, 모두가 따라올 수 있는 길을 열겠습니다.'
|
|
|
|
|
],
|
|
|
|
|
choices: [
|
|
|
|
|
{
|
|
|
|
|
id: 'draw-return-routes',
|
|
|
|
|
label: '귀환로를 먼저 그린다',
|
|
|
|
|
response: '제갈량은 북방 지도 위에 귀환로를 먼저 긋고, 조운은 기병이 회수 신호를 받을 수 있는 고개와 들판을 표시했습니다.',
|
|
|
|
|
rewardExp: 25
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'assign-mobile-screen',
|
|
|
|
|
label: '기동 엄호대를 편성한다',
|
|
|
|
|
response: '조운은 빠른 장수들을 묶어 엄호 순서를 맞췄고, 제갈량은 그 부대가 본대와 너무 멀어지지 않도록 신호 체계를 정했습니다.',
|
|
|
|
|
rewardExp: 24
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'northern-prep-huang-ma-ledgers',
|
|
|
|
|
title: '군량과 명분',
|
|
|
|
|
availableAfterBattleIds: [campBattleIds.fiftyFourth],
|
|
|
|
|
availableDuringSteps: ['northern-campaign-prep-camp'],
|
|
|
|
|
unitIds: ['huang-quan', 'ma-liang'],
|
|
|
|
|
bondId: 'zhuge-liang__huang-quan_final-capture',
|
|
|
|
|
rewardExp: 70,
|
|
|
|
|
lines: [
|
|
|
|
|
'황권: 북벌은 군량이 먼저 무너지는 싸움이 될 수 있습니다. 익주와 한중의 장부를 따로 보면 길이 끊깁니다.',
|
|
|
|
|
'마량: 장부만으로도 부족합니다. 백성에게 왜 이 길을 여는지 말이 남아야 합니다.',
|
|
|
|
|
'황권: 그러면 저는 수량을 맞추고, 마량은 그 수량이 원망이 되지 않게 말을 맞추십시오.'
|
|
|
|
|
],
|
|
|
|
|
choices: [
|
|
|
|
|
{
|
|
|
|
|
id: 'balance-hanzhong-granary',
|
|
|
|
|
label: '한중 창고 장부를 맞춘다',
|
|
|
|
|
response: '황권은 한중 창고의 군량과 수레 수를 다시 계산했고, 마량은 징발 대신 보상 문서를 함께 붙였습니다.',
|
|
|
|
|
rewardExp: 23
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'write-campaign-proclamation',
|
|
|
|
|
label: '북벌 고시문을 쓴다',
|
|
|
|
|
response: '마량은 북벌의 뜻을 짧게 적었고, 황권은 그 글 아래에 각 고을이 감당할 수 있는 보급 한계를 명확히 남겼습니다.',
|
|
|
|
|
rewardExp: 24
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'northern-prep-wang-madai-ridges',
|
|
|
|
|
title: '산길과 말발굽',
|
|
|
|
|
availableAfterBattleIds: [campBattleIds.fiftyFourth],
|
|
|
|
|
availableDuringSteps: ['northern-campaign-prep-camp'],
|
|
|
|
|
unitIds: ['wang-ping', 'ma-dai'],
|
|
|
|
|
bondId: 'zhao-yun__ma-dai_final-capture',
|
|
|
|
|
rewardExp: 66,
|
|
|
|
|
lines: [
|
|
|
|
|
'왕평: 북쪽 산길은 지도보다 좁고, 좁은 길은 기병에게도 보병에게도 함정이 됩니다.',
|
|
|
|
|
'마대: 그렇다면 말이 달릴 길보다 멈출 곳을 먼저 익히겠습니다.',
|
|
|
|
|
'왕평: 산길을 아는 자가 앞을 보고, 말을 아는 자가 뒤를 닫으면 본대가 길을 잃지 않을 것입니다.'
|
|
|
|
|
],
|
|
|
|
|
choices: [
|
|
|
|
|
{
|
|
|
|
|
id: 'mark-ridge-halts',
|
|
|
|
|
label: '고개 정지선을 표시한다',
|
|
|
|
|
response: '왕평은 좁은 고개마다 정지선을 표시했고, 마대는 기병이 그 선에서 멈추는 짧은 훈련을 반복했습니다.',
|
|
|
|
|
rewardExp: 22
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'test-scout-rotation',
|
|
|
|
|
label: '정찰 교대 순서를 시험한다',
|
|
|
|
|
response: '마대는 기병 정찰의 교대 시간을 줄였고, 왕평은 산길마다 보병이 이어받을 위치를 정했습니다.',
|
|
|
|
|
rewardExp: 21
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
@@ -8106,6 +8217,68 @@ const campVisits: CampVisitDefinition[] = [
|
|
|
|
|
itemRewards: ['콩 16', '탁주 4']
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'northern-prep-hanzhong-granary',
|
|
|
|
|
title: '한중 창고 점검',
|
|
|
|
|
location: '한중 군량 창고',
|
|
|
|
|
availableAfterBattleIds: [campBattleIds.fiftyFourth],
|
|
|
|
|
availableDuringSteps: ['northern-campaign-prep-camp'],
|
|
|
|
|
bondId: 'zhuge-liang__huang-quan_final-capture',
|
|
|
|
|
description: '제갈량과 황권이 북벌의 첫 출진 전에 한중 창고, 수레, 보급 기한을 다시 점검합니다.',
|
|
|
|
|
lines: [
|
|
|
|
|
'황권: 남중 평정 장부가 끝났다고 곧장 북쪽으로 달리면 군량이 먼저 말라 버립니다.',
|
|
|
|
|
'제갈량: 북벌은 뜻만으로 움직이지 않습니다. 뜻을 오래 버티게 하는 창고가 있어야 합니다.',
|
|
|
|
|
'한중 창고에는 수레 표식과 군량 날짜가 새로 적힌 장부가 펼쳐졌습니다.'
|
|
|
|
|
],
|
|
|
|
|
choices: [
|
|
|
|
|
{
|
|
|
|
|
id: 'count-northern-wagons',
|
|
|
|
|
label: '수레와 군량을 다시 센다',
|
|
|
|
|
response: '황권은 수레 수와 군량 날짜를 맞췄고, 제갈량은 먼저 보낼 물자와 남길 물자를 구분했습니다.',
|
|
|
|
|
bondExp: 58,
|
|
|
|
|
gold: 2140,
|
|
|
|
|
itemRewards: ['콩 20', '상처약 12']
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'reserve-field-rations',
|
|
|
|
|
label: '예비 군량을 따로 둔다',
|
|
|
|
|
response: '제갈량은 전공을 서두르는 병사들에게 예비 군량의 의미를 설명했고, 황권은 대기 부대 몫을 따로 봉했습니다.',
|
|
|
|
|
bondExp: 56,
|
|
|
|
|
itemRewards: ['탁주 6', '상처약 16']
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'northern-prep-memorial-table',
|
|
|
|
|
title: '출사표 초안',
|
|
|
|
|
location: '승상부 서안',
|
|
|
|
|
availableAfterBattleIds: [campBattleIds.fiftyFourth],
|
|
|
|
|
availableDuringSteps: ['northern-campaign-prep-camp'],
|
|
|
|
|
bondId: 'zhao-yun__zhuge-liang',
|
|
|
|
|
description: '제갈량이 북쪽으로 나아갈 뜻을 정리하고, 조운이 그 뜻을 실제 출진 순서로 옮길 준비를 합니다.',
|
|
|
|
|
lines: [
|
|
|
|
|
'제갈량: 글은 군을 움직이지 못하지만, 군이 왜 움직이는지는 잊지 않게 합니다.',
|
|
|
|
|
'조운: 그 뜻이 분명하면 장수들도 무리한 공을 다투지 않을 것입니다.',
|
|
|
|
|
'서안 위에는 출사표의 초안과 첫 출진 편성표가 함께 놓였습니다.'
|
|
|
|
|
],
|
|
|
|
|
choices: [
|
|
|
|
|
{
|
|
|
|
|
id: 'draft-memorial-oath',
|
|
|
|
|
label: '출사표 초안을 다듬는다',
|
|
|
|
|
response: '제갈량은 군주의 은혜와 한실의 뜻을 다시 적었고, 조운은 그 글이 장수들의 출진 순서와 어긋나지 않게 살폈습니다.',
|
|
|
|
|
bondExp: 58,
|
|
|
|
|
itemRewards: ['북벌 고시문 1']
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
id: 'match-sortie-order',
|
|
|
|
|
label: '출진 순서를 맞춘다',
|
|
|
|
|
response: '조운은 선발대와 본대의 거리를 정했고, 제갈량은 무리한 돌파보다 오래 버티는 행군표를 먼저 승인했습니다.',
|
|
|
|
|
bondExp: 56,
|
|
|
|
|
gold: 1980,
|
|
|
|
|
itemRewards: ['콩 18']
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
@@ -8903,6 +9076,9 @@ export class CampScene extends Phaser.Scene {
|
|
|
|
|
if (this.campaign?.step === 'baidi-entrustment-camp') {
|
|
|
|
|
return '백제성 유탁 후 군영';
|
|
|
|
|
}
|
|
|
|
|
if (this.campaign?.step === 'northern-campaign-prep-camp') {
|
|
|
|
|
return '북벌 준비 군영';
|
|
|
|
|
}
|
|
|
|
|
if (battleId === campBattleIds.fiftyFourth) {
|
|
|
|
|
return '남중 평정 후 군영';
|
|
|
|
|
}
|
|
|
|
|
@@ -9522,10 +9698,12 @@ export class CampScene extends Phaser.Scene {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private nextSortieRule(scenario = this.nextSortieScenario()) {
|
|
|
|
|
if (!scenario) {
|
|
|
|
|
return defaultSortieRule;
|
|
|
|
|
if (scenario) {
|
|
|
|
|
return sortieRulesByBattleId[scenario.id] ?? defaultSortieRule;
|
|
|
|
|
}
|
|
|
|
|
return sortieRulesByBattleId[scenario.id] ?? defaultSortieRule;
|
|
|
|
|
const step = this.campaign?.step;
|
|
|
|
|
const flowStep = this.currentSortieFlow().campaignStep;
|
|
|
|
|
return (step && sortieRulesByCampaignStep[step]) || (flowStep && sortieRulesByCampaignStep[flowStep]) || defaultSortieRule;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private requiredSortieUnitIdsFor(scenario = this.nextSortieScenario()) {
|
|
|
|
|
@@ -9660,6 +9838,7 @@ export class CampScene extends Phaser.Scene {
|
|
|
|
|
const selectedIds = new Set(selectedUnits.map((unit) => unit.id));
|
|
|
|
|
const scenario = this.nextSortieScenario();
|
|
|
|
|
const rule = this.nextSortieRule(scenario);
|
|
|
|
|
const flow = this.currentSortieFlow();
|
|
|
|
|
const reserveFocus = this.reserveTrainingFocusDefinition();
|
|
|
|
|
const availableIds = new Set(allAllies.map((unit) => unit.id));
|
|
|
|
|
const recommended = rule.recommended.filter((entry) => availableIds.has(entry.unitId));
|
|
|
|
|
@@ -9738,7 +9917,7 @@ export class CampScene extends Phaser.Scene {
|
|
|
|
|
reserveTrainingExpPreview: reserveFocus.expGained,
|
|
|
|
|
reserveTrainingEquipmentPreview: reserveFocus.equipmentExpGained,
|
|
|
|
|
reserveTrainingBondPreview: reserveFocus.bondExpGained,
|
|
|
|
|
objectiveLine: scenario ? `${scenario.title} · ${scenario.victoryConditionLabel}` : '다음 전투 정보 없음',
|
|
|
|
|
objectiveLine: scenario ? `${scenario.title} · ${scenario.victoryConditionLabel}` : `${flow.title} · 군영 의정`,
|
|
|
|
|
formationSlots,
|
|
|
|
|
warnings
|
|
|
|
|
};
|
|
|
|
|
@@ -9874,7 +10053,14 @@ export class CampScene extends Phaser.Scene {
|
|
|
|
|
|
|
|
|
|
private sortieChecklist(): SortieChecklistItem[] {
|
|
|
|
|
const units = this.currentUnits().filter((unit) => unit.faction === 'ally');
|
|
|
|
|
const liuBei = units.find((unit) => unit.id === 'liu-bei');
|
|
|
|
|
const sortieAllies = this.sortieAllies();
|
|
|
|
|
const requiredUnitIds = [...this.requiredSortieUnitIdsFor()].filter((id) => sortieAllies.some((unit) => unit.id === id));
|
|
|
|
|
const requiredUnits = requiredUnitIds.map((id) => sortieAllies.find((unit) => unit.id === id)).filter(Boolean) as UnitData[];
|
|
|
|
|
const requiredLabel = requiredUnits.length > 0 && !requiredUnitIds.includes('liu-bei') ? '필수 무장 확인' : '유비 생존';
|
|
|
|
|
const requiredComplete = requiredUnits.length > 0 ? requiredUnits.every((unit) => unit.hp > 0) : true;
|
|
|
|
|
const requiredDetail = requiredUnits.length > 0
|
|
|
|
|
? requiredUnits.map((unit) => `${unit.name} ${unit.hp}/${unit.maxHp}`).join(', ')
|
|
|
|
|
: '필수 무장 없음';
|
|
|
|
|
const injured = units.filter((unit) => unit.hp < unit.maxHp);
|
|
|
|
|
const supplyCount = campSupplies.reduce((total, supply) => total + this.inventoryAmount(supply.label), 0);
|
|
|
|
|
const availableDialogues = this.availableCampDialogues();
|
|
|
|
|
@@ -9884,9 +10070,9 @@ export class CampScene extends Phaser.Scene {
|
|
|
|
|
const selected = this.selectedSortieUnits();
|
|
|
|
|
return [
|
|
|
|
|
{
|
|
|
|
|
label: '유비 생존',
|
|
|
|
|
complete: Boolean(liuBei && liuBei.hp > 0),
|
|
|
|
|
detail: liuBei ? `병력 ${liuBei.hp}/${liuBei.maxHp}` : '유비 없음'
|
|
|
|
|
label: requiredLabel,
|
|
|
|
|
complete: requiredComplete,
|
|
|
|
|
detail: requiredDetail
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: '부상 장수 확인',
|
|
|
|
|
@@ -9895,7 +10081,7 @@ export class CampScene extends Phaser.Scene {
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
label: '출전 구성',
|
|
|
|
|
complete: selected.some((unit) => unit.id === 'liu-bei') && selected.length > 0,
|
|
|
|
|
complete: requiredUnitIds.every((id) => selected.some((unit) => unit.id === id)) && selected.length > 0,
|
|
|
|
|
detail: selected.length > 0 ? selected.map((unit) => unit.name).join(', ') : '출전 무장 선택 필요'
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
@@ -9940,7 +10126,15 @@ export class CampScene extends Phaser.Scene {
|
|
|
|
|
private startVictoryStory() {
|
|
|
|
|
const flow = this.currentSortieFlow();
|
|
|
|
|
if (!this.ensureSortieSelectionSaved()) {
|
|
|
|
|
this.showCampNotice('출전할 무장을 선택하세요. 유비는 반드시 출전해야 합니다.');
|
|
|
|
|
const availableIds = new Set(this.sortieAllies().map((unit) => unit.id));
|
|
|
|
|
const requiredNames = [...this.requiredSortieUnitIdsFor()]
|
|
|
|
|
.filter((unitId) => availableIds.has(unitId))
|
|
|
|
|
.map((unitId) => this.unitName(unitId));
|
|
|
|
|
this.showCampNotice(
|
|
|
|
|
requiredNames.length > 0
|
|
|
|
|
? `출전할 무장을 선택하세요. ${requiredNames.join(', ')}는 반드시 포함되어야 합니다.`
|
|
|
|
|
: '출전할 무장을 선택하세요.'
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -10256,7 +10450,14 @@ export class CampScene extends Phaser.Scene {
|
|
|
|
|
|
|
|
|
|
private activeTimelineChapterIndex(completedIds = this.completedBattleIds()) {
|
|
|
|
|
const latestBattleId = this.campaign?.latestBattleId ?? this.report?.battleId;
|
|
|
|
|
const nextBattleId = this.currentSortieFlow().nextBattleId;
|
|
|
|
|
const flow = this.currentSortieFlow();
|
|
|
|
|
const nextBattleId = flow.nextBattleId;
|
|
|
|
|
if (!nextBattleId && flow.campaignStep === 'northern-campaign-prep-camp') {
|
|
|
|
|
const northernIndex = campaignTimelineChapters.findIndex((chapter) => chapter.id === 'northern-campaign');
|
|
|
|
|
if (northernIndex >= 0) {
|
|
|
|
|
return northernIndex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!nextBattleId && latestBattleId) {
|
|
|
|
|
const latestChapterIndex = campaignTimelineChapters.findIndex((chapter) =>
|
|
|
|
|
chapter.battleIds.includes(latestBattleId as BattleScenarioId)
|
|
|
|
|
@@ -11033,11 +11234,13 @@ export class CampScene extends Phaser.Scene {
|
|
|
|
|
|
|
|
|
|
private sortieAllies() {
|
|
|
|
|
const scenario = this.nextSortieScenario();
|
|
|
|
|
const rule = this.nextSortieRule(scenario);
|
|
|
|
|
const scenarioAllyIds = scenario
|
|
|
|
|
? new Set(scenario.units.filter((unit) => unit.faction === 'ally').map((unit) => unit.id))
|
|
|
|
|
: undefined;
|
|
|
|
|
const excludedUnitIds = new Set(rule.excludedUnitIds ?? []);
|
|
|
|
|
return this.currentUnits().filter(
|
|
|
|
|
(unit) => unit.faction === 'ally' && unit.hp > 0 && (!scenarioAllyIds || scenarioAllyIds.has(unit.id))
|
|
|
|
|
(unit) => unit.faction === 'ally' && unit.hp > 0 && !excludedUnitIds.has(unit.id) && (!scenarioAllyIds || scenarioAllyIds.has(unit.id))
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|