HumanBreak/script/resource.ts

246 lines
6.7 KiB
TypeScript
Raw Normal View History

2023-05-31 12:09:47 +08:00
import fs from 'fs-extra';
import { uniqueSymbol } from './utils.js';
2023-06-05 11:18:44 +08:00
import { basename, extname, resolve } from 'path';
import { dirname } from 'path';
import motaConfig from '../mota.config.js';
2023-05-31 12:09:47 +08:00
type ResorceType =
2023-06-04 23:06:56 +08:00
| 'bgms'
| 'sounds'
2023-05-31 12:09:47 +08:00
| 'autotiles'
| 'images'
| 'materials'
| 'tilesets'
| 'animates'
| 'fonts';
const SYMBOL = uniqueSymbol();
const MAX_SIZE = 100 * (1 << 20) - 20 * (1 << 10);
2023-06-04 23:06:56 +08:00
const baseDir = './dist';
2023-05-31 12:09:47 +08:00
let totalSize = 0;
2023-06-04 23:06:56 +08:00
type Stats = fs.Stats & { name?: string };
2023-05-31 12:09:47 +08:00
export async function splitResorce(compress: boolean = false) {
2023-06-09 22:59:20 +08:00
await fs.ensureDir('./dist-resource');
await fs.emptyDir('./dist-resource');
2023-05-31 12:09:47 +08:00
const folder = await fs.stat('./dist');
totalSize = folder.size;
2023-06-07 18:17:45 +08:00
if (totalSize < MAX_SIZE) return;
2023-05-31 12:09:47 +08:00
await doSplit(compress);
}
async function sortDir(dir: string, ext?: string[]) {
const path = await fs.readdir(dir);
2023-06-04 23:06:56 +08:00
const stats: Stats[] = [];
2023-05-31 12:09:47 +08:00
for await (const one of path) {
if (ext && !ext.includes(extname(one))) continue;
2023-06-07 18:17:45 +08:00
if (one === 'bg.jpg') continue;
2023-05-31 12:09:47 +08:00
const stat = await fs.stat(resolve(dir, one));
if (!stat.isFile()) continue;
2023-06-04 23:06:56 +08:00
const status: Stats = {
...stat,
name: one
};
stats.push(status);
2023-05-31 12:09:47 +08:00
}
return stats.sort((a, b) => b.size - a.size);
}
2023-06-04 23:06:56 +08:00
async function calResourceSize() {
return (
await Promise.all(
[
'animates',
'autotiles',
'bgms',
'fonts',
'images',
'materials',
'sounds',
'tilesets'
].map(v => fs.stat(resolve('./dist/project/', v)))
)
).reduce((pre, cur) => pre + cur.size, 0);
}
async function doSplit(compress: boolean) {
let size = await calResourceSize();
await fs.emptyDir('./dist-resource');
const priority: ResorceType[] = [
'materials',
'tilesets',
'autotiles',
'animates',
'images',
'sounds',
'fonts',
'bgms'
];
const dirInfo: Record<ResorceType, Stats[]> = Object.fromEntries(
await Promise.all(
priority.map(async v => [
v,
await sortDir(resolve(baseDir, 'project', v))
])
)
);
let currSize = 0;
2023-06-05 11:18:44 +08:00
const length = Object.fromEntries(
priority.map(v => [v, dirInfo[v].length])
);
const soruceIndex: Record<string, number> = {};
2023-06-04 23:06:56 +08:00
const split = async (index: number): Promise<void> => {
size -= currSize;
currSize = 0;
const cut: string[] = [];
2023-06-05 11:18:44 +08:00
const mapped: ResorceType[] = [];
while (1) {
const toCut = priority.find(
v => dirInfo[v].length > 0 && !mapped.includes(v)
);
if (!toCut) break;
mapped.push(toCut);
const l = dirInfo[toCut].length;
const data: string[] = [];
let pass = 0;
2023-06-04 23:06:56 +08:00
while (1) {
2023-06-05 11:18:44 +08:00
const stats = dirInfo[toCut];
const stat = stats[pass];
if (!stat) {
break;
2023-06-04 23:06:56 +08:00
}
2023-06-05 11:18:44 +08:00
if (currSize + stat.size >= MAX_SIZE) {
pass++;
continue;
}
data.push(`${toCut}/${stat.name}`);
stats.splice(pass, 1);
currSize += stat.size;
}
if (l === length[toCut] && dirInfo[toCut].length === 0) {
soruceIndex[`${toCut}.*`] = index;
} else {
data.map(v => (soruceIndex[v] = index));
2023-06-04 23:06:56 +08:00
}
2023-06-05 11:18:44 +08:00
cut.push(...data);
}
2023-06-04 23:06:56 +08:00
const dir = `./dist-resource/${index}`;
await fs.ensureDir(dir);
await Promise.all(priority.map(v => fs.ensureDir(resolve(dir, v))));
await Promise.all(
cut.map(v =>
fs.move(
resolve('./dist/project', v),
2023-06-05 11:18:44 +08:00
resolve(
dir,
dirname(v),
`${basename(v).split('.')[0]}-${SYMBOL}${extname(v)}`
)
2023-06-04 23:06:56 +08:00
)
)
);
// 生成可发布结构
await generatePublishStructure(dir, index);
2023-06-04 23:06:56 +08:00
if (Object.values(dirInfo).every(v => v.length === 0)) return;
else return split(index + 1);
};
await split(0);
2023-06-05 11:18:44 +08:00
await rewriteMain(soruceIndex);
}
async function rewriteMain(sourceIndex: Record<string, number>) {
const main = await fs.readFile('./dist/main.js', 'utf-8');
const res = main
.replace(/this\.USE_RESOURCE\s*\=\s*false/, 'this.USE_RESOURCE = true')
.replace(
/this\.RESOURCE_URL\s*\=\s*'.*'/,
`this.RESOURCE_URL = '/games/${motaConfig.resourceName}'`
2023-06-05 11:18:44 +08:00
)
.replace(
/this\.RESOURCE_SYMBOL\s*\=\s*'.*'/,
`this.RESOURCE_SYMBOL = '${SYMBOL}'`
)
.replace(
/this\.RESOURCE_INDEX\s*\=\s*\{.*\}/,
`this.RESOURCE_INDEX = ${JSON.stringify(sourceIndex, void 0, 8)}`
);
await fs.writeFile('./dist/main.js', res, 'utf-8');
2023-06-04 23:06:56 +08:00
}
async function generatePublishStructure(dir: string, index: number) {
await fs.mkdir(resolve(dir, 'libs'));
await fs.mkdir(resolve(dir, 'libs/thirdparty'));
await fs.mkdir(resolve(dir, 'project'));
await Promise.all(
[
'autotiles',
'images',
'materials',
'animates',
'fonts',
'floors',
'tilesets',
'sounds',
'bgms'
].map(v => fs.mkdir(resolve(dir, 'project', v)))
);
await fs.writeFile(
resolve(dir, 'project/icons.js'),
`var icons_4665ee12_3a1f_44a4_bea3_0fccba634dc1 =
{
"autotile": {
}
}`,
'utf-8'
);
await fs.writeFile(
resolve(dir, 'project/floors/none.js'),
'"none"',
'utf-8'
);
await fs.writeFile(resolve(dir, 'libs/none.js'), '"none"', 'utf-8');
await fs.copyFile(
'./script/template/main.js',
resolve(dir, 'project/main.js')
);
const data = await fs.readFile('./script/template/data.js', 'utf-8');
await fs.writeFile(
resolve(dir, 'project/data.js'),
data.replace('@name', `${motaConfig.resourceName}${index}`)
);
await fs.copyFile(
'./script/template/lz-string.min.js',
resolve(dir, 'libs/thirdparty/lz-string.min.js')
);
await Promise.all(
['animates', 'images', 'materials', 'sounds', 'tilesets'].map(v => {
fs.copyFile(
'./script/template/.h5data',
resolve(dir, `project/${v}/${v}.h5data`)
);
})
);
}