feat: upgrade combat and item icons
This commit is contained in:
@@ -8,13 +8,26 @@ const server = await createServer({
|
||||
});
|
||||
|
||||
const minimumReadableIconFrameSize = 96;
|
||||
const expectedV2IconFrames = {
|
||||
attack: 4,
|
||||
hit: 16,
|
||||
critical: 17,
|
||||
counter: 19,
|
||||
fire: 24,
|
||||
bean: 27,
|
||||
salve: 28,
|
||||
wine: 29
|
||||
};
|
||||
|
||||
try {
|
||||
const { battleUiIconManifest } = await server.ssrLoadModule('/src/game/data/battleUiIcons.ts');
|
||||
const { battleUiIconManifest, battleUiIconTextureForSize } = await server.ssrLoadModule('/src/game/data/battleUiIcons.ts');
|
||||
const { usableCatalog } = await server.ssrLoadModule('/src/game/data/battleUsables.ts');
|
||||
const { itemCatalog, equipmentSlots } = await server.ssrLoadModule('/src/game/data/battleItems.ts');
|
||||
const errors = [];
|
||||
|
||||
validateBattleUiIconAtlas(errors, battleUiIconManifest);
|
||||
validateBattleUiIconTextureRouting(errors, battleUiIconManifest, battleUiIconTextureForSize);
|
||||
validateBattleUsableIcons(errors, usableCatalog, battleUiIconManifest.frames);
|
||||
validateItemCatalog(errors, itemCatalog, equipmentSlots);
|
||||
validateGeneratedEquipmentIcons(errors, itemCatalog);
|
||||
|
||||
@@ -54,6 +67,11 @@ function validateBattleUiIconAtlas(errors, manifest) {
|
||||
);
|
||||
assertPositiveInteger(errors, manifest.columns, 'battleUiIconManifest.columns');
|
||||
assertPositiveInteger(errors, manifest.rows, 'battleUiIconManifest.rows');
|
||||
assertNonEmptyString(errors, manifest.microSource, 'battleUiIconManifest.microSource');
|
||||
assertNonEmptyString(errors, manifest.microTextureKey, 'battleUiIconManifest.microTextureKey');
|
||||
assertPositiveInteger(errors, manifest.microFrameWidth, 'battleUiIconManifest.microFrameWidth');
|
||||
assertPositiveInteger(errors, manifest.microFrameHeight, 'battleUiIconManifest.microFrameHeight');
|
||||
assertPositiveInteger(errors, manifest.microMaxDisplaySize, 'battleUiIconManifest.microMaxDisplaySize');
|
||||
|
||||
let dimensions;
|
||||
try {
|
||||
@@ -72,6 +90,26 @@ function validateBattleUiIconAtlas(errors, manifest) {
|
||||
errors.push(`battle UI icon atlas must be 8-bit RGBA PNG color type 6, got bitDepth=${dimensions.bitDepth} colorType=${dimensions.colorType}`);
|
||||
}
|
||||
|
||||
let microDimensions;
|
||||
try {
|
||||
microDimensions = readPngDimensions(manifest.microSource);
|
||||
} catch (error) {
|
||||
errors.push(`battle UI micro icon atlas cannot be read at "${manifest.microSource}": ${error.message}`);
|
||||
return;
|
||||
}
|
||||
const expectedMicroWidth = manifest.columns * manifest.microFrameWidth;
|
||||
const expectedMicroHeight = manifest.rows * manifest.microFrameHeight;
|
||||
if (microDimensions.width !== expectedMicroWidth || microDimensions.height !== expectedMicroHeight) {
|
||||
errors.push(
|
||||
`battle UI micro icon atlas is ${microDimensions.width}x${microDimensions.height}, expected ${expectedMicroWidth}x${expectedMicroHeight}`
|
||||
);
|
||||
}
|
||||
if (microDimensions.bitDepth !== 8 || microDimensions.colorType !== 6) {
|
||||
errors.push(
|
||||
`battle UI micro icon atlas must be 8-bit RGBA PNG color type 6, got bitDepth=${microDimensions.bitDepth} colorType=${microDimensions.colorType}`
|
||||
);
|
||||
}
|
||||
|
||||
const frameLimit = manifest.columns * manifest.rows;
|
||||
Object.entries(manifest.frames).forEach(([key, frame]) => {
|
||||
assertNonEmptyString(errors, key, 'battle UI icon key');
|
||||
@@ -79,6 +117,79 @@ function validateBattleUiIconAtlas(errors, manifest) {
|
||||
errors.push(`battle UI icon "${key}" frame ${frame} is outside atlas frame range 0..${frameLimit - 1}`);
|
||||
}
|
||||
});
|
||||
const coveredFrames = new Set(Object.values(manifest.frames));
|
||||
for (let frame = 0; frame < frameLimit; frame += 1) {
|
||||
if (!coveredFrames.has(frame)) {
|
||||
errors.push(`battle UI icon atlas frame ${frame} has no manifest key`);
|
||||
}
|
||||
}
|
||||
|
||||
validateBattleUiIconSources(errors, manifest);
|
||||
}
|
||||
|
||||
function validateBattleUiIconSources(errors, manifest) {
|
||||
const sources = manifest.v2Sources;
|
||||
if (!sources || typeof sources !== 'object' || Array.isArray(sources)) {
|
||||
errors.push('battleUiIconManifest.v2Sources must be an object');
|
||||
return;
|
||||
}
|
||||
|
||||
const entries = Object.entries(sources);
|
||||
if (entries.length !== 8) {
|
||||
errors.push(`battleUiIconManifest.v2Sources must contain 8 source icons, got ${entries.length}`);
|
||||
}
|
||||
|
||||
const sourceFrames = new Set();
|
||||
entries.forEach(([key, source]) => {
|
||||
const context = `battleUiIconManifest.v2Sources.${key}`;
|
||||
if (expectedV2IconFrames[key] !== source.frame) {
|
||||
errors.push(`${context}.frame must remain ${expectedV2IconFrames[key]}, got ${source.frame}`);
|
||||
}
|
||||
if (manifest.frames[key] !== source.frame) {
|
||||
errors.push(`${context}.frame ${source.frame} does not match battleUiIconManifest.frames.${key} ${manifest.frames[key]}`);
|
||||
}
|
||||
if (sourceFrames.has(source.frame)) {
|
||||
errors.push(`${context}.frame duplicates frame ${source.frame}`);
|
||||
}
|
||||
sourceFrames.add(source.frame);
|
||||
assertNonEmptyString(errors, source.source, `${context}.source`);
|
||||
|
||||
let dimensions;
|
||||
try {
|
||||
dimensions = readPngDimensions(source.source);
|
||||
} catch (error) {
|
||||
errors.push(`${context}.source cannot be read at "${source.source}": ${error.message}`);
|
||||
return;
|
||||
}
|
||||
if (dimensions.width !== dimensions.height || dimensions.width < 512) {
|
||||
errors.push(`${context}.source must be a square master of at least 512px, got ${dimensions.width}x${dimensions.height}`);
|
||||
}
|
||||
if (dimensions.bitDepth !== 8 || dimensions.colorType !== 6) {
|
||||
errors.push(`${context}.source must be an 8-bit RGBA PNG, got bitDepth=${dimensions.bitDepth} colorType=${dimensions.colorType}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateBattleUiIconTextureRouting(errors, manifest, textureForSize) {
|
||||
if (textureForSize(14) !== manifest.microTextureKey || textureForSize(manifest.microMaxDisplaySize) !== manifest.microTextureKey) {
|
||||
errors.push('battle UI icons at or below microMaxDisplaySize must use the micro atlas');
|
||||
}
|
||||
if (textureForSize(manifest.microMaxDisplaySize + 1) !== manifest.textureKey || textureForSize(70) !== manifest.textureKey) {
|
||||
errors.push('battle UI icons above microMaxDisplaySize must use the full atlas');
|
||||
}
|
||||
}
|
||||
|
||||
function validateBattleUsableIcons(errors, usableCatalog, frames) {
|
||||
Object.entries(usableCatalog).forEach(([id, usable]) => {
|
||||
const context = `usableCatalog.${id}.iconKey`;
|
||||
assertNonEmptyString(errors, usable.iconKey, context);
|
||||
if (!(usable.iconKey in frames)) {
|
||||
errors.push(`${context} references unknown battle UI icon "${usable.iconKey}"`);
|
||||
}
|
||||
if (usable.command === 'item' && usable.iconKey !== id) {
|
||||
errors.push(`${context} must use the item's dedicated icon key "${id}", got "${usable.iconKey}"`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateItemCatalog(errors, itemCatalog, equipmentSlots) {
|
||||
|
||||
Reference in New Issue
Block a user