feat: improve first-session combat and visual assets
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { readdirSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { createServer } from 'vite';
|
||||
|
||||
const validCutsceneKinds = new Set(['muster', 'operation', 'victory']);
|
||||
@@ -15,7 +17,8 @@ const server = await createServer({
|
||||
try {
|
||||
const scenarioModule = await server.ssrLoadModule('/src/game/data/scenario.ts');
|
||||
const { battleScenarios } = await server.ssrLoadModule('/src/game/data/battles.ts');
|
||||
const { storyBackgroundAssets, storyBackgroundKeysFor } = await server.ssrLoadModule('/src/game/data/storyAssets.ts');
|
||||
const { storyBackgroundAssets, storyBackgroundKeyForPage, storyBackgroundKeysFor } =
|
||||
await server.ssrLoadModule('/src/game/data/storyAssets.ts');
|
||||
const { portraitAssetEntriesForKey, portraitKeyForSpeaker } = await server.ssrLoadModule('/src/game/data/portraitAssets.ts');
|
||||
const { storyCutsceneActorProfileFor } = await server.ssrLoadModule('/src/game/data/storyCutscenes.ts');
|
||||
const { hasUnitSheetAsset } = await server.ssrLoadModule('/src/game/data/unitAssets.ts');
|
||||
@@ -24,6 +27,8 @@ try {
|
||||
const storySources = collectStorySources(scenarioModule, battleScenarios);
|
||||
const uniquePageIds = new Set();
|
||||
const usedBackgroundKeys = new Set();
|
||||
const selectedBackgroundKeys = new Set();
|
||||
const selectedBackgroundUrlsByBase = new Map();
|
||||
const usedPortraitKeys = new Set();
|
||||
const usedCutsceneActorIds = new Set();
|
||||
let pageCount = 0;
|
||||
@@ -44,9 +49,12 @@ try {
|
||||
index,
|
||||
page,
|
||||
usedBackgroundKeys,
|
||||
selectedBackgroundKeys,
|
||||
selectedBackgroundUrlsByBase,
|
||||
usedPortraitKeys,
|
||||
usedCutsceneActorIds,
|
||||
storyBackgroundAssets,
|
||||
storyBackgroundKeyForPage,
|
||||
storyBackgroundKeysFor,
|
||||
portraitAssetEntriesForKey,
|
||||
portraitKeyForSpeaker,
|
||||
@@ -59,6 +67,9 @@ try {
|
||||
});
|
||||
});
|
||||
|
||||
validateStoryBackgroundVariantCoverage(errors, selectedBackgroundKeys);
|
||||
validateRepeatedStoryBackgroundDistribution(errors, selectedBackgroundUrlsByBase);
|
||||
|
||||
if (errors.length) {
|
||||
console.error(`Story asset data verification failed with ${errors.length} issue(s):`);
|
||||
errors.slice(0, 100).forEach((error) => console.error(`- ${error}`));
|
||||
@@ -69,7 +80,8 @@ try {
|
||||
} else {
|
||||
console.log(
|
||||
`Verified ${pageCount} story page references (${uniquePageIds.size} unique pages), ` +
|
||||
`${cutsceneCount} cutscenes, ${usedBackgroundKeys.size} backgrounds, ` +
|
||||
`${cutsceneCount} cutscenes, ${usedBackgroundKeys.size} background candidates, ` +
|
||||
`${selectedBackgroundKeys.size} selected backgrounds, ` +
|
||||
`${usedPortraitKeys.size} portrait keys, and ${usedCutsceneActorIds.size} cutscene actor profiles.`
|
||||
);
|
||||
}
|
||||
@@ -131,7 +143,16 @@ function validateStoryPage(context) {
|
||||
}
|
||||
|
||||
function validateBackground(context, pageContext) {
|
||||
const { errors, page, usedBackgroundKeys, storyBackgroundAssets, storyBackgroundKeysFor } = context;
|
||||
const {
|
||||
errors,
|
||||
page,
|
||||
usedBackgroundKeys,
|
||||
selectedBackgroundKeys,
|
||||
selectedBackgroundUrlsByBase,
|
||||
storyBackgroundAssets,
|
||||
storyBackgroundKeyForPage,
|
||||
storyBackgroundKeysFor
|
||||
} = context;
|
||||
if (!page.background) {
|
||||
return;
|
||||
}
|
||||
@@ -139,8 +160,71 @@ function validateBackground(context, pageContext) {
|
||||
const keys = storyBackgroundKeysFor(page.background).filter((key) => storyBackgroundAssets[key]);
|
||||
if (keys.length === 0) {
|
||||
errors.push(`${pageContext}: missing story background asset "${page.background}"`);
|
||||
return;
|
||||
}
|
||||
keys.forEach((key) => usedBackgroundKeys.add(key));
|
||||
|
||||
const selectedKey = storyBackgroundKeyForPage(page.background, page.id);
|
||||
const repeatedSelection = storyBackgroundKeyForPage(page.background, page.id);
|
||||
if (selectedKey !== repeatedSelection) {
|
||||
errors.push(`${pageContext}: story background selection must be deterministic`);
|
||||
}
|
||||
if (!keys.includes(selectedKey)) {
|
||||
errors.push(`${pageContext}: selected background "${selectedKey}" is not a candidate for "${page.background}"`);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedUrl = storyBackgroundAssets[selectedKey];
|
||||
if (!selectedUrl) {
|
||||
errors.push(`${pageContext}: selected background "${selectedKey}" has no runtime asset URL`);
|
||||
return;
|
||||
}
|
||||
|
||||
selectedBackgroundKeys.add(selectedKey);
|
||||
const backgroundGroup = selectedBackgroundUrlsByBase.get(page.background) ?? {
|
||||
pageCount: 0,
|
||||
candidateUrls: new Set(),
|
||||
selectedUrls: new Set()
|
||||
};
|
||||
backgroundGroup.pageCount += 1;
|
||||
keys.forEach((key) => backgroundGroup.candidateUrls.add(storyBackgroundAssets[key]));
|
||||
backgroundGroup.selectedUrls.add(selectedUrl);
|
||||
selectedBackgroundUrlsByBase.set(page.background, backgroundGroup);
|
||||
}
|
||||
|
||||
function validateStoryBackgroundVariantCoverage(errors, selectedBackgroundKeys) {
|
||||
const firstVariantNumber = 33;
|
||||
const lastVariantNumber = 134;
|
||||
const expectedVariantCount = lastVariantNumber - firstVariantNumber + 1;
|
||||
const variantKeys = readdirSync(join('src', 'assets', 'images', 'story'), { withFileTypes: true })
|
||||
.filter((entry) => entry.isFile())
|
||||
.map((entry) => entry.name.match(/^(\d+)-(.+)\.png$/i))
|
||||
.filter((match) => match && Number(match[1]) >= firstVariantNumber && Number(match[1]) <= lastVariantNumber)
|
||||
.map((match) => `story-${match[2]}`)
|
||||
.sort();
|
||||
|
||||
if (variantKeys.length !== expectedVariantCount) {
|
||||
errors.push(
|
||||
`story background variants ${firstVariantNumber}-${lastVariantNumber}: expected ${expectedVariantCount} files, ` +
|
||||
`found ${variantKeys.length}`
|
||||
);
|
||||
}
|
||||
|
||||
variantKeys.forEach((key) => {
|
||||
if (!selectedBackgroundKeys.has(key)) {
|
||||
errors.push(`story background variant "${key}" is never selected by a campaign page`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateRepeatedStoryBackgroundDistribution(errors, selectedBackgroundUrlsByBase) {
|
||||
selectedBackgroundUrlsByBase.forEach(({ pageCount, candidateUrls, selectedUrls }, baseKey) => {
|
||||
if (pageCount > 1 && candidateUrls.size > 1 && selectedUrls.size < 2) {
|
||||
errors.push(
|
||||
`${baseKey}: ${pageCount} repeated pages should use at least two of ${candidateUrls.size} background variants`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validatePortrait(context, pageContext) {
|
||||
|
||||
Reference in New Issue
Block a user