187 lines
8.8 KiB
JavaScript
187 lines
8.8 KiB
JavaScript
import { BASE_UNITS, nativeBuild } from './design.mjs';
|
|
|
|
export const FOLLOWUP_SUITES = ['conditional', 'premium', 'sensitivity', 'recruit', 'attrition', 'counter'];
|
|
export function buildFollowupPlan(catalog, suite) {
|
|
const cells = [];
|
|
const add = (cell) => cells.push({ id: String(cells.length).padStart(5, '0'), ...cell });
|
|
const subjects = [1100, 1200, 1300, 1400, 1501];
|
|
if (suite === 'conditional') {
|
|
// Same effect registry, but opponents meet region/city conditions absent from basic-family screens.
|
|
const candidates = [
|
|
...catalog.war.map((trait) => ({ key: trait.key, generalPatch: { special2: trait.key } })),
|
|
...catalog.items.map((item) => ({ key: item.key, generalPatch: { [item.slot]: item.key } })),
|
|
];
|
|
for (const candidate of candidates)
|
|
for (const unit of subjects)
|
|
for (const opponent of [1104, 1407]) {
|
|
for (const role of ['attack', 'defend'])
|
|
add({
|
|
...candidate,
|
|
category: suite,
|
|
unit,
|
|
opponent,
|
|
build: nativeBuild(unit),
|
|
opponentBuild: nativeBuild(opponent),
|
|
role,
|
|
mode: 'field',
|
|
paired: true,
|
|
});
|
|
}
|
|
for (const opponent of [1100, 1101, 1104, 1407])
|
|
for (const role of ['attack', 'defend']) {
|
|
add({
|
|
category: suite,
|
|
key: 'che_척사+che_척사_오악진형도',
|
|
unit: 1100,
|
|
build: 'martial',
|
|
opponent,
|
|
opponentBuild: nativeBuild(opponent),
|
|
role,
|
|
mode: 'field',
|
|
paired: true,
|
|
generalPatch: { special2: 'che_척사', item: 'che_척사_오악진형도' },
|
|
factorialPatches: [{ special2: 'che_척사' }, { item: 'che_척사_오악진형도' }],
|
|
});
|
|
}
|
|
} else if (suite === 'premium') {
|
|
const keys = [
|
|
'che_명마_12_옥란백용구',
|
|
'che_명마_07_백상',
|
|
'che_불굴_상편',
|
|
'che_위압_조목삭',
|
|
'che_회피_태평요술',
|
|
];
|
|
for (const key of keys) {
|
|
const item = catalog.items.find((entry) => entry.key === key);
|
|
if (!item) throw new Error(`Unknown premium candidate ${key}`);
|
|
// Horse: persistent buyable +6 horse. Tool: empty slot and recurring consumable are separate controls.
|
|
const controls = item.slot === 'horse' ? ['che_명마_06_흑색마'] : [null, 'che_훈련_청주'];
|
|
for (const control of controls)
|
|
for (const unit of subjects)
|
|
for (const opponent of [1100, 1200, 1300, 1400, 1104, 1407]) {
|
|
for (const role of ['attack', 'defend'])
|
|
add({
|
|
category: suite,
|
|
key,
|
|
unit,
|
|
opponent,
|
|
build: nativeBuild(unit),
|
|
opponentBuild: nativeBuild(opponent),
|
|
role,
|
|
mode: 'field',
|
|
paired: true,
|
|
baseline: control ?? 'empty',
|
|
generalPatch: { [item.slot]: key },
|
|
controlPatch: { [item.slot]: control },
|
|
});
|
|
}
|
|
}
|
|
} else if (suite === 'counter') {
|
|
for (const special2 of [null, 'che_반계', 'che_격노'])
|
|
for (const unit of subjects)
|
|
for (const opponent of [1100, 1300, 1400, 1407])
|
|
for (const role of ['attack', 'defend'])
|
|
add({
|
|
category: suite,
|
|
key: 'che_진압_박혁론',
|
|
unit,
|
|
opponent,
|
|
build: nativeBuild(unit),
|
|
opponentBuild: nativeBuild(opponent),
|
|
role,
|
|
mode: 'field',
|
|
paired: true,
|
|
condition: special2 ?? 'no-opponent-trait',
|
|
opponentPatch: { special2 },
|
|
generalPatch: { item: 'che_진압_박혁론' },
|
|
});
|
|
} else if (suite === 'attrition') {
|
|
for (const key of ['che_명마_12_옥란백용구', 'che_불굴_상편']) {
|
|
const item = catalog.items.find((entry) => entry.key === key);
|
|
if (!item) throw new Error(`Unknown attrition candidate ${key}`);
|
|
const control = item.slot === 'horse' ? 'che_명마_06_흑색마' : null;
|
|
for (const unit of subjects)
|
|
for (const crewFraction of [1, 0.5, 0.25])
|
|
for (const opponent of [1100, 1200, 1300, 1407])
|
|
for (const role of ['attack', 'defend'])
|
|
add({
|
|
category: suite,
|
|
key,
|
|
unit,
|
|
opponent,
|
|
build: nativeBuild(unit),
|
|
opponentBuild: nativeBuild(opponent),
|
|
role,
|
|
mode: 'field',
|
|
paired: true,
|
|
budget: 'recruited',
|
|
wallet: 100000,
|
|
quality: 'trained',
|
|
crewFraction,
|
|
condition: `remaining-${crewFraction}`,
|
|
baseline: control ?? 'empty',
|
|
generalPatch: { [item.slot]: key },
|
|
controlPatch: { [item.slot]: control },
|
|
});
|
|
}
|
|
} else if (suite === 'sensitivity') {
|
|
for (const [condition, patch] of Object.entries({
|
|
early: { year: 183, tech: 1000, dex: 0, readiness: 80, statScale: 0.8 },
|
|
trained: { year: 200, tech: 3000, dex: 10000, readiness: 100 },
|
|
late: { year: 220, tech: 6000, dex: 50000, readiness: 110, statScale: 1.4 },
|
|
}))
|
|
for (const unit of BASE_UNITS)
|
|
for (const opponent of BASE_UNITS) {
|
|
for (const role of ['attack', 'defend'])
|
|
add({
|
|
category: suite,
|
|
key: String(unit),
|
|
unit,
|
|
opponent,
|
|
build: nativeBuild(unit),
|
|
opponentBuild: nativeBuild(opponent),
|
|
role,
|
|
mode: 'field',
|
|
condition,
|
|
...patch,
|
|
});
|
|
}
|
|
} else if (suite === 'recruit') {
|
|
const variants = [
|
|
{ key: 'che_징병', generalPatch: { special2: 'che_징병' } },
|
|
{ key: 'che_징병_낙주', generalPatch: { item: 'che_징병_낙주' } },
|
|
{
|
|
key: 'che_명마_12_옥란백용구',
|
|
generalPatch: { horse: 'che_명마_12_옥란백용구' },
|
|
controlPatch: { horse: 'che_명마_06_흑색마' },
|
|
},
|
|
...['보병', '궁병', '기병'].map((name) => ({
|
|
key: `che_${name}`,
|
|
generalPatch: { special2: `che_${name}` },
|
|
})),
|
|
];
|
|
for (const candidate of variants)
|
|
for (const unit of subjects)
|
|
for (const wallet of [1500, 5000]) {
|
|
for (const quality of ['fresh', 'trained'])
|
|
for (const opponent of [1100, 1300, 1400]) {
|
|
add({
|
|
...candidate,
|
|
category: suite,
|
|
unit,
|
|
opponent,
|
|
build: nativeBuild(unit),
|
|
opponentBuild: nativeBuild(opponent),
|
|
role: 'attack',
|
|
mode: 'field',
|
|
paired: true,
|
|
budget: 'recruited',
|
|
wallet,
|
|
quality,
|
|
});
|
|
}
|
|
}
|
|
} else throw new Error(`Unknown follow-up suite ${suite}`);
|
|
return cells;
|
|
}
|