123 lines
3.4 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";
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;
};
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",
);
}
return { outDir, missionCount: sorted.length };
}