134 lines
3.7 KiB
TypeScript
134 lines
3.7 KiB
TypeScript
import { join } from "@std/path";
|
|
import { ensureDir } from "@std/fs/ensure-dir";
|
|
import type {
|
|
CampaignInfoJson,
|
|
CampaignInventory,
|
|
CampainMapPosition,
|
|
ChallengeJson,
|
|
InventoryMission,
|
|
} from "./campaign-types.ts";
|
|
import {
|
|
defaultChallengeModifiers,
|
|
mergeChallengeModifiers,
|
|
} from "./campaign-types.ts";
|
|
import { writeMissionProgressionMarkdown } from "./mission-markdown.ts";
|
|
|
|
function defaultLinearMapPositions(n: number): CampainMapPosition[] {
|
|
const out: CampainMapPosition[] = [];
|
|
for (let i = 0; i < n; i++) {
|
|
const child = i < n - 1 ? [i + 1] : [];
|
|
out.push({
|
|
x: i * 140,
|
|
y: 0,
|
|
scale: 1,
|
|
numberPortion: i,
|
|
letterPortion: "",
|
|
childNodes: child,
|
|
});
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/** Ensure mapPositions length matches mission count; pad with defaults if partial. */
|
|
export function resolveMapPositions(
|
|
inv: CampaignInventory,
|
|
): CampainMapPosition[] {
|
|
const n = inv.missions.length;
|
|
if (n === 0) return [];
|
|
const existing = inv.mapPositions ?? [];
|
|
if (existing.length === 0) {
|
|
return defaultLinearMapPositions(n);
|
|
}
|
|
if (existing.length === n) return existing;
|
|
if (existing.length > n) return existing.slice(0, n);
|
|
const base = defaultLinearMapPositions(n);
|
|
for (let i = 0; i < existing.length; i++) {
|
|
base[i] = { ...base[i], ...existing[i] };
|
|
}
|
|
return base;
|
|
}
|
|
|
|
function missionToChallenge(m: InventoryMission): ChallengeJson {
|
|
const mods = mergeChallengeModifiers(
|
|
defaultChallengeModifiers(),
|
|
m.modifiers,
|
|
);
|
|
const chall: ChallengeJson = {
|
|
name: m.name,
|
|
songid: m.songid,
|
|
hash: m.hash ?? "",
|
|
customDownloadURL: m.customDownloadURL ?? "",
|
|
characteristic: m.characteristic,
|
|
difficulty: m.difficulty,
|
|
modifiers: mods,
|
|
unlockMap: m.unlockMap ?? false,
|
|
requirements: m.requirements ?? [],
|
|
};
|
|
if (m.allowStandardLevel !== undefined) {
|
|
chall.allowStandardLevel = m.allowStandardLevel;
|
|
}
|
|
if (m.externalModifiers && Object.keys(m.externalModifiers).length > 0) {
|
|
chall.externalModifiers = m.externalModifiers;
|
|
}
|
|
if (m.unlockableItems && m.unlockableItems.length > 0) {
|
|
chall.unlockableItems = m.unlockableItems;
|
|
}
|
|
if (m.challengeInfo !== undefined) chall.challengeInfo = m.challengeInfo;
|
|
return chall;
|
|
}
|
|
|
|
export type GenerateResult = {
|
|
outDir: string;
|
|
missionCount: number;
|
|
markdownPath: string;
|
|
markdownWarnings: string[];
|
|
};
|
|
|
|
export async function generateCampaignToDist(
|
|
inv: CampaignInventory,
|
|
distRoot: string,
|
|
): Promise<GenerateResult> {
|
|
const outDir = join(distRoot, inv.outputFolderName);
|
|
await ensureDir(outDir);
|
|
|
|
const sorted = [...inv.missions].sort((a, b) => a.index - b.index);
|
|
const mapPositions = resolveMapPositions({ ...inv, missions: sorted });
|
|
|
|
const info: CampaignInfoJson = {
|
|
name: inv.info.name,
|
|
desc: inv.info.desc,
|
|
bigDesc: inv.info.bigDesc,
|
|
allUnlocked: inv.info.allUnlocked,
|
|
mapHeight: inv.info.mapHeight,
|
|
backgroundAlpha: inv.info.backgroundAlpha,
|
|
lightColor: inv.info.lightColor,
|
|
unlockGate: inv.info.unlockGate,
|
|
mapPositions,
|
|
useStandardLevel: inv.info.useStandardLevel,
|
|
customMissionLeaderboard: inv.info.customMissionLeaderboard,
|
|
};
|
|
|
|
await Deno.writeTextFile(
|
|
join(outDir, "info.json"),
|
|
JSON.stringify(info, null, 2) + "\n",
|
|
);
|
|
|
|
for (const m of sorted) {
|
|
const chall = missionToChallenge(m);
|
|
await Deno.writeTextFile(
|
|
join(outDir, `${m.index}.json`),
|
|
JSON.stringify(chall, null, 2) + "\n",
|
|
);
|
|
}
|
|
|
|
const { markdownPath, warnings: markdownWarnings } =
|
|
await writeMissionProgressionMarkdown(outDir, inv, mapPositions, sorted);
|
|
|
|
return {
|
|
outDir,
|
|
missionCount: sorted.length,
|
|
markdownPath,
|
|
markdownWarnings,
|
|
};
|
|
}
|