mirror of
https://github.com/LostCityRS/Engine-TS
synced 2026-08-16 08:26:04 -04:00
411 lines
15 KiB
TypeScript
411 lines
15 KiB
TypeScript
import fs from 'fs';
|
|
import { basename, dirname } from 'path';
|
|
|
|
import Environment from '#/util/Environment.js';
|
|
import { PackFile } from '#tools/pack/PackFileBase.js';
|
|
import { listFilesExt, loadDirExtFull } from '#tools/pack/Parse.js';
|
|
import { fileExists, fileStats } from '#tools/pack/FsCache.js';
|
|
import { printDebug, printError } from '#/util/Logger.js';
|
|
// import { printWarning } from '#/util/Logger.js';
|
|
|
|
function validateFilesPack(pack: PackFile, paths: string[], ext: string, verify: boolean = true): void {
|
|
pack.load(`${Environment.BUILD_SRC_DIR}/pack/${pack.type}.pack`);
|
|
|
|
for (const path of paths) {
|
|
const files = listFilesExt(path, ext);
|
|
|
|
const fileNames = new Set(files.map(x => basename(x, ext)));
|
|
for (let i = 0; i < files.length; i++) {
|
|
files[i] = files[i].substring(files[i].lastIndexOf('/') + 1); // strip file path
|
|
files[i] = files[i].substring(0, files[i].length - ext.length); // strip extension
|
|
fileNames.add(files[i]);
|
|
}
|
|
|
|
if (verify) {
|
|
for (let i = 0; i < files.length; i++) {
|
|
const name = files[i];
|
|
|
|
if (!pack.names.has(name)) {
|
|
throw new Error(`${pack.type}: ${name} is missing an ID line, you may need to edit ${Environment.BUILD_SRC_DIR}/pack/${pack.type}.pack`);
|
|
}
|
|
}
|
|
|
|
if (Environment.BUILD_VERIFY_PACK) {
|
|
for (const name of pack.names) {
|
|
if (!fileNames.has(name)) {
|
|
// printWarning(`${pack.type}: ${name} was not found on your disk`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pack.save();
|
|
}
|
|
|
|
function validateImagePack(pack: PackFile, path: string, ext: string): void {
|
|
pack.load(`${Environment.BUILD_SRC_DIR}/pack/${pack.type}.pack`);
|
|
|
|
const files = listFilesExt(path, ext);
|
|
|
|
const fileNames = new Set(files.map(x => basename(x, ext)));
|
|
for (let i = 0; i < files.length; i++) {
|
|
if (basename(dirname(files[i])) === 'meta') {
|
|
continue;
|
|
}
|
|
|
|
files[i] = files[i].substring(files[i].lastIndexOf('/') + 1); // strip file path
|
|
files[i] = files[i].substring(0, files[i].length - ext.length); // strip extension
|
|
fileNames.add(files[i]);
|
|
|
|
const name = files[i];
|
|
if (!pack.names.has(name)) {
|
|
throw new Error(`${pack.type}: ${name} is missing an ID line, you may need to edit ${Environment.BUILD_SRC_DIR}/pack/${pack.type}.pack`);
|
|
}
|
|
}
|
|
|
|
if (Environment.BUILD_VERIFY_PACK) {
|
|
for (const name of pack.names) {
|
|
if (!fileNames.has(name)) {
|
|
throw new Error(`${pack.type}: ${name} was not found on your disk, you may need to edit ${Environment.BUILD_SRC_DIR}/pack/${pack.type}.pack`);
|
|
}
|
|
}
|
|
}
|
|
|
|
pack.save();
|
|
}
|
|
|
|
function validateConfigPack(pack: PackFile, ext: string, transmitted: boolean = false): void {
|
|
const names = crawlConfigNames(ext);
|
|
const configNames = new Set(names);
|
|
|
|
pack.load(`${Environment.BUILD_SRC_DIR}/pack/${pack.type}.pack`);
|
|
|
|
if (!transmitted || (!Environment.BUILD_VERIFY && transmitted)) {
|
|
for (let i = 0; i < names.length; i++) {
|
|
if (!pack.names.has(names[i])) {
|
|
pack.register(pack.max++, names[i]);
|
|
}
|
|
}
|
|
|
|
pack.refreshNames();
|
|
}
|
|
|
|
const missing = [];
|
|
for (let i = 0; i < names.length; i++) {
|
|
const name = names[i];
|
|
|
|
if (!pack.names.has(name) && !name.startsWith('cert_')) {
|
|
missing.push(name);
|
|
}
|
|
}
|
|
|
|
if (Environment.BUILD_VERIFY && missing.length > 0) {
|
|
if (missing.length > 1) {
|
|
printError(`Missing ${pack.type} pack IDs for:`);
|
|
} else {
|
|
printError(`Missing ${pack.type} pack ID for:`);
|
|
}
|
|
|
|
for (const name of missing) {
|
|
printDebug(`[${name}]`);
|
|
}
|
|
|
|
throw new Error(`You may need to edit ${Environment.BUILD_SRC_DIR}/pack/${pack.type}.pack`);
|
|
}
|
|
|
|
for (const name of pack.names) {
|
|
if (Environment.BUILD_VERIFY && !configNames.has(name) && !name.startsWith('cert_')) {
|
|
throw new Error(`${pack.type}: ${name} was not found in any ${ext} files, you may need to edit ${Environment.BUILD_SRC_DIR}/pack/${pack.type}.pack`);
|
|
}
|
|
}
|
|
|
|
if (!transmitted || (!Environment.BUILD_VERIFY && transmitted)) {
|
|
pack.save();
|
|
}
|
|
}
|
|
|
|
function validateCategoryPack(pack: PackFile) {
|
|
if (
|
|
shouldBuild(`${Environment.BUILD_SRC_DIR}/scripts`, '.loc', `${Environment.BUILD_SRC_DIR}/pack/category.pack`) ||
|
|
shouldBuild(`${Environment.BUILD_SRC_DIR}/scripts`, '.npc', `${Environment.BUILD_SRC_DIR}/pack/category.pack`) ||
|
|
shouldBuild(`${Environment.BUILD_SRC_DIR}/scripts`, '.obj', `${Environment.BUILD_SRC_DIR}/pack/category.pack`)
|
|
) {
|
|
const categories = crawlConfigCategories();
|
|
for (let i = 0; i < categories.length; i++) {
|
|
pack.register(i, categories[i]);
|
|
}
|
|
pack.refreshNames();
|
|
pack.save();
|
|
} else {
|
|
pack.load(`${Environment.BUILD_SRC_DIR}/pack/category.pack`);
|
|
}
|
|
}
|
|
|
|
function validateInterfacePack(pack: PackFile) {
|
|
pack.load(`${Environment.BUILD_SRC_DIR}/pack/interface.pack`);
|
|
|
|
loadDirExtFull(`${Environment.BUILD_SRC_DIR}/scripts`, '.if', (lines: string[], file: string) => {
|
|
if (Environment.BUILD_VERIFY_FOLDER) {
|
|
const parent = basename(dirname(dirname(file)));
|
|
const dir = basename(dirname(file));
|
|
if (dir !== 'interfaces' && parent !== 'interfaces') {
|
|
throw new Error(`Interface file ${file} must be located inside an "interfaces" directory.`);
|
|
}
|
|
}
|
|
|
|
const inter = basename(file, '.if');
|
|
if (!pack.names.has(inter)) {
|
|
throw new Error(`${Environment.BUILD_SRC_DIR}/pack/interface.pack is missing ID for interface ${inter} from ${file}`);
|
|
}
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
|
|
if (line.startsWith('[')) {
|
|
const com = line.substring(1, line.length - 1);
|
|
const name = `${inter}:${com}`;
|
|
|
|
if (!pack.names.has(name)) {
|
|
throw new Error(`${Environment.BUILD_SRC_DIR}/pack/interface.pack is missing ID for component ${name} from ${file}`);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// todo: validate triggers, names, and/or reuse IDs?
|
|
function regenScriptPack(pack: PackFile) {
|
|
pack.load(`${Environment.BUILD_SRC_DIR}/pack/script.pack`);
|
|
|
|
const names = crawlConfigNames('.rs2', true);
|
|
for (let i = 0; i < names.length; i++) {
|
|
if (!pack.names.has(names[i])) {
|
|
pack.register(pack.max++, names[i]);
|
|
}
|
|
}
|
|
pack.refreshNames();
|
|
pack.save();
|
|
}
|
|
|
|
export const AnimSetPack = new PackFile('animset', validateFilesPack, [`${Environment.BUILD_SRC_DIR}/models`], '.anim');
|
|
export const AnimPack = new PackFile('anim', validateFilesPack, [`${Environment.BUILD_SRC_DIR}/models`], '.frame', false);
|
|
export const BasePack = new PackFile('base', validateFilesPack, [`${Environment.BUILD_SRC_DIR}/models`], '.base', false);
|
|
export const CategoryPack = new PackFile('category', validateCategoryPack);
|
|
export const DbRowPack = new PackFile('dbrow', validateConfigPack, '.dbrow');
|
|
export const DbTablePack = new PackFile('dbtable', validateConfigPack, '.dbtable');
|
|
export const EnumPack = new PackFile('enum', validateConfigPack, '.enum');
|
|
export const FloPack = new PackFile('flo', validateConfigPack, '.flo', true);
|
|
export const HuntPack = new PackFile('hunt', validateConfigPack, '.hunt');
|
|
export const IdkPack = new PackFile('idk', validateConfigPack, '.idk', true);
|
|
export const InterfacePack = new PackFile('interface', validateInterfacePack);
|
|
export const InvPack = new PackFile('inv', validateConfigPack, '.inv');
|
|
export const LocPack = new PackFile('loc', validateConfigPack, '.loc', true);
|
|
export const MesAnimPack = new PackFile('mesanim', validateConfigPack, '.mesanim');
|
|
export const MapPack = new PackFile('map', validateFilesPack, [`${Environment.BUILD_SRC_DIR}/maps`], '.jm2', false);
|
|
export const MidiPack = new PackFile('midi', validateFilesPack, [`${Environment.BUILD_SRC_DIR}/jingles`, `${Environment.BUILD_SRC_DIR}/songs`], '.mid');
|
|
export const ModelPack = new PackFile('model', validateFilesPack, [`${Environment.BUILD_SRC_DIR}/models`], '.ob2');
|
|
export const NpcPack = new PackFile('npc', validateConfigPack, '.npc', true);
|
|
export const ObjPack = new PackFile('obj', validateConfigPack, '.obj', true);
|
|
export const ParamPack = new PackFile('param', validateConfigPack, '.param');
|
|
export const ScriptPack = new PackFile('script', regenScriptPack);
|
|
export const SeqPack = new PackFile('seq', validateConfigPack, '.seq', true);
|
|
export const SynthPack = new PackFile('synth', validateFilesPack, [`${Environment.BUILD_SRC_DIR}/synth`], '.synth');
|
|
export const SpotAnimPack = new PackFile('spotanim', validateConfigPack, '.spotanim', true);
|
|
export const StructPack = new PackFile('struct', validateConfigPack, '.struct');
|
|
export const TexturePack = new PackFile('texture', validateImagePack, `${Environment.BUILD_SRC_DIR}/textures`, '.png');
|
|
export const VarpPack = new PackFile('varp', validateConfigPack, '.varp', true);
|
|
export const VarbitPack = new PackFile('varbit', validateConfigPack, '.varbit', true);
|
|
export const VarnPack = new PackFile('varn', validateConfigPack, '.varn');
|
|
export const VarsPack = new PackFile('vars', validateConfigPack, '.vars');
|
|
|
|
export function revalidatePack() {
|
|
AnimPack.reload();
|
|
BasePack.reload();
|
|
CategoryPack.reload();
|
|
DbRowPack.reload();
|
|
DbTablePack.reload();
|
|
EnumPack.reload();
|
|
FloPack.reload();
|
|
HuntPack.reload();
|
|
IdkPack.reload();
|
|
InterfacePack.reload();
|
|
InvPack.reload();
|
|
LocPack.reload();
|
|
MesAnimPack.reload();
|
|
ModelPack.reload();
|
|
NpcPack.reload();
|
|
ObjPack.reload();
|
|
ParamPack.reload();
|
|
ScriptPack.reload();
|
|
SeqPack.reload();
|
|
SynthPack.reload();
|
|
SpotAnimPack.reload();
|
|
StructPack.reload();
|
|
TexturePack.reload();
|
|
VarpPack.reload();
|
|
VarnPack.reload();
|
|
VarsPack.reload();
|
|
VarbitPack.reload();
|
|
}
|
|
|
|
export function crawlConfigNames(ext: string, includeBrackets = false) {
|
|
const names: string[] = [];
|
|
|
|
loadDirExtFull(`${Environment.BUILD_SRC_DIR}/scripts`, ext, (lines: string[], file: string) => {
|
|
if (file === `${Environment.BUILD_SRC_DIR}/scripts/engine.rs2`) {
|
|
// these command signatures are specifically for the compiler to have type information
|
|
return;
|
|
}
|
|
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
|
|
if (line.startsWith('[')) {
|
|
let name = line.substring(0, line.indexOf(']') + 1);
|
|
if (!includeBrackets) {
|
|
name = name.substring(1, name.length - 1);
|
|
}
|
|
|
|
if (Environment.BUILD_VERIFY_FOLDER) {
|
|
const parentParent = basename(dirname(dirname(dirname(file))));
|
|
const parent = basename(dirname(dirname(file)));
|
|
const dir = basename(dirname(file));
|
|
if ((dir !== '_unpack' && parent !== '_unpack' && parentParent !== '_unpack') && ext !== '.flo') {
|
|
if (ext === '.rs2' && dir !== 'scripts' && parent !== 'scripts') {
|
|
throw new Error(`Script file ${file} must be located inside a "scripts" directory.`);
|
|
} else if (ext !== '.rs2' && dir !== 'configs' && parent !== 'configs') {
|
|
throw new Error(`Config file ${file} must be located inside a "configs" directory.`);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (names.indexOf(name) === -1) {
|
|
names.push(name);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return names;
|
|
}
|
|
|
|
function crawlConfigCategories() {
|
|
const names: string[] = [];
|
|
|
|
loadDirExtFull(`${Environment.BUILD_SRC_DIR}/scripts`, '.loc', (lines: string[]) => {
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
|
|
if (line.startsWith('category=')) {
|
|
const name = line.substring('category='.length);
|
|
|
|
if (names.indexOf(name) === -1) {
|
|
names.push(name);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
loadDirExtFull(`${Environment.BUILD_SRC_DIR}/scripts`, '.npc', (lines: string[]) => {
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
|
|
if (line.startsWith('category=')) {
|
|
const name = line.substring('category='.length);
|
|
|
|
if (names.indexOf(name) === -1) {
|
|
names.push(name);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
loadDirExtFull(`${Environment.BUILD_SRC_DIR}/scripts`, '.obj', (lines: string[]) => {
|
|
for (let i = 0; i < lines.length; i++) {
|
|
const line = lines[i];
|
|
|
|
if (line.startsWith('category=')) {
|
|
const name = line.substring('category='.length);
|
|
|
|
if (names.indexOf(name) === -1) {
|
|
names.push(name);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
return names;
|
|
}
|
|
|
|
export function getModified(path: string) {
|
|
if (!fileExists(path)) {
|
|
return 0;
|
|
}
|
|
|
|
const stats = fileStats(path);
|
|
return stats.mtimeMs;
|
|
}
|
|
|
|
export function getLatestModified(path: string, ext: string) {
|
|
const files = listFilesExt(path, ext);
|
|
|
|
let latest = 0;
|
|
for (let i = 0; i < files.length; i++) {
|
|
const file = files[i];
|
|
const stats = fileStats(file);
|
|
|
|
if (stats.mtimeMs > latest) {
|
|
latest = stats.mtimeMs;
|
|
}
|
|
}
|
|
|
|
return latest;
|
|
}
|
|
|
|
export function shouldBuild(path: string, ext: string, out: string) {
|
|
if (!fileExists(out)) {
|
|
return true;
|
|
}
|
|
|
|
const stats = fileStats(out);
|
|
const latest = getLatestModified(path, ext);
|
|
|
|
return stats.mtimeMs < latest;
|
|
}
|
|
|
|
export function shouldBuildFile(src: string, dest: string) {
|
|
if (!fileExists(dest)) {
|
|
return true;
|
|
}
|
|
|
|
const stats = fileStats(dest);
|
|
const srcStats = fileStats(src);
|
|
|
|
return stats.mtimeMs < srcStats.mtimeMs;
|
|
}
|
|
|
|
export function shouldBuildFileAny(path: string, dest: string) {
|
|
if (!fileExists(dest)) {
|
|
return true;
|
|
}
|
|
|
|
const entries = fs.readdirSync(path, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
const target = `${entry.parentPath}/${entry.name}`;
|
|
|
|
if (entry.isDirectory()) {
|
|
const subdir = shouldBuildFileAny(target, dest);
|
|
if (subdir) {
|
|
return true;
|
|
}
|
|
} else {
|
|
if (shouldBuildFile(target, dest)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|