218 lines
6.4 KiB
JavaScript
218 lines
6.4 KiB
JavaScript
const fs = require("fs");
|
|
const path = require("path");
|
|
const chokidar = require("chokidar");
|
|
|
|
class DatapackLoader {
|
|
constructor() {
|
|
this.registry = {
|
|
items: new Map(),
|
|
recipes: new Map(),
|
|
skills: new Map(),
|
|
dungeons: new Map(),
|
|
enemies: new Map(),
|
|
rooms: new Map(),
|
|
languages: new Map(),
|
|
manifest: {},
|
|
};
|
|
this.rootPath = null;
|
|
}
|
|
|
|
init(datapacksRoot, io = null) {
|
|
this.rootPath = datapacksRoot || path.join(__dirname, "../../datapacks");
|
|
if (!fs.existsSync(this.rootPath)) return;
|
|
this.loadAll();
|
|
if (io) this.watch(io);
|
|
}
|
|
|
|
deepMerge(target, source) {
|
|
for (const key in source) {
|
|
if (source[key] instanceof Object && key in target) {
|
|
Object.assign(source[key], this.deepMerge(target[key], source[key]));
|
|
}
|
|
}
|
|
Object.assign(target || {}, source);
|
|
return target;
|
|
}
|
|
|
|
loadAll() {
|
|
Object.values(this.registry).forEach((val) => {
|
|
if (val instanceof Map) val.clear();
|
|
});
|
|
this.registry.manifest = {};
|
|
let manifestCount = 0;
|
|
|
|
const packs = fs
|
|
.readdirSync(this.rootPath, { withFileTypes: true })
|
|
.filter((d) => d.isDirectory())
|
|
.map((d) => d.name)
|
|
.sort((a, b) => (a === "original" ? -1 : 1));
|
|
|
|
packs.forEach((packName) => {
|
|
const packPath = path.join(this.rootPath, packName);
|
|
const dataPath = path.join(packPath, "data");
|
|
const langPath = path.join(packPath, "assets/languages");
|
|
const manifestPath = path.join(packPath, "manifest.json");
|
|
|
|
if (fs.existsSync(manifestPath)) {
|
|
try {
|
|
const manifestContent = JSON.parse(
|
|
fs.readFileSync(manifestPath, "utf8"),
|
|
);
|
|
this.deepMerge(this.registry.manifest, manifestContent);
|
|
manifestCount++;
|
|
} catch (err) {
|
|
console.error(`❌ Manifest Error [${packName}]:`, err.message);
|
|
}
|
|
}
|
|
|
|
if (fs.existsSync(dataPath)) this.loadRecursive(dataPath, packName);
|
|
if (fs.existsSync(langPath)) this.loadLanguages(langPath);
|
|
});
|
|
|
|
console.log(
|
|
`🚀 Registry Ready: ${this.registry.items.size} Items, ${this.registry.dungeons.size} Dungeons, ${this.registry.languages.size} Langs, ${manifestCount} Manifests`,
|
|
);
|
|
}
|
|
getRecipe(id) {
|
|
return this.registry.recipes.get(id);
|
|
}
|
|
|
|
getRecipesByCategory(category) {
|
|
const allRecipes = Array.from(this.registry.recipes.values());
|
|
return allRecipes.filter((r) => r.category === category);
|
|
}
|
|
|
|
getRecipeCategories() {
|
|
const allRecipes = Array.from(this.registry.recipes.values());
|
|
const categories = new Set(
|
|
allRecipes.map((r) => r.category).filter(Boolean),
|
|
);
|
|
return Array.from(categories);
|
|
}
|
|
loadLanguages(langPath) {
|
|
try {
|
|
const files = fs.readdirSync(langPath).filter((f) => f.endsWith(".json"));
|
|
files.forEach((file) => {
|
|
const langCode = path.basename(file, ".json");
|
|
const fullPath = path.join(langPath, file);
|
|
const newContent = JSON.parse(fs.readFileSync(fullPath, "utf8"));
|
|
|
|
if (!this.registry.languages.has(langCode)) {
|
|
this.registry.languages.set(langCode, {});
|
|
}
|
|
|
|
const currentDict = this.registry.languages.get(langCode);
|
|
this.deepMerge(currentDict, newContent);
|
|
});
|
|
} catch (err) {
|
|
console.error(`❌ Language Load Error:`, err.message);
|
|
}
|
|
}
|
|
|
|
watch(io) {
|
|
const watcher = chokidar.watch(this.rootPath, {
|
|
persistent: true,
|
|
ignoreInitial: true,
|
|
});
|
|
watcher.on("all", (event, filePath) => {
|
|
if (filePath.endsWith(".json")) {
|
|
console.log(`🔄 Datapack change detected: ${path.basename(filePath)}`);
|
|
this.loadAll();
|
|
io.emit(
|
|
"admin:log",
|
|
`System: Datapacks reloaded (${path.basename(filePath)})`,
|
|
);
|
|
io.emit("system:data_updated", this.getStaticData());
|
|
}
|
|
});
|
|
}
|
|
|
|
loadRecursive(dirPath, packName) {
|
|
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dirPath, entry.name);
|
|
if (entry.isDirectory()) {
|
|
this.loadRecursive(fullPath, packName);
|
|
} else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
this.parseFile(fullPath, packName);
|
|
}
|
|
}
|
|
}
|
|
|
|
parseFile(filePath, packName) {
|
|
try {
|
|
const fileContent = fs.readFileSync(filePath, "utf8");
|
|
const json = JSON.parse(fileContent);
|
|
const typeKey = Object.keys(json)[0];
|
|
const data = json[typeKey];
|
|
if (!data) return;
|
|
|
|
const fullId = `${packName}:${data.id}`;
|
|
|
|
switch (typeKey) {
|
|
case "armour":
|
|
case "plating":
|
|
case "materials":
|
|
case "weapons":
|
|
data.type = typeKey;
|
|
this.registry.items.set(fullId, data);
|
|
this.registry.items.set(data.id, data);
|
|
break;
|
|
case "recipe":
|
|
const recipeId = json.craft?.id || data.id;
|
|
this.registry.recipes.set(recipeId, { ...json.craft, ...data });
|
|
break;
|
|
case "skills":
|
|
this.registry.skills.set(fullId, data);
|
|
this.registry.skills.set(data.id, data);
|
|
break;
|
|
case "dungeon":
|
|
this.registry.dungeons.set(fullId, data);
|
|
this.registry.dungeons.set(data.id, data);
|
|
break;
|
|
case "hostile":
|
|
this.registry.enemies.set(fullId, data);
|
|
this.registry.enemies.set(data.id, data);
|
|
break;
|
|
case "rooms":
|
|
this.registry.rooms.set(fullId, data);
|
|
this.registry.rooms.set(data.id, data);
|
|
break;
|
|
}
|
|
} catch (err) {
|
|
console.error(`❌ Parse Error [${filePath}]:`, err.message);
|
|
}
|
|
}
|
|
|
|
getItem(id) {
|
|
return this.registry.items.get(id);
|
|
}
|
|
getEnemy(id) {
|
|
return this.registry.enemies.get(id);
|
|
}
|
|
getDungeon(id) {
|
|
return this.registry.dungeons.get(id);
|
|
}
|
|
getRoom(id) {
|
|
return this.registry.rooms.get(id);
|
|
}
|
|
getRecipes() {
|
|
return Array.from(this.registry.recipes.values());
|
|
}
|
|
|
|
getStaticData() {
|
|
return {
|
|
items: Array.from(this.registry.items.values()),
|
|
recipes: Array.from(this.registry.recipes.values()),
|
|
skills: Array.from(this.registry.skills.values()),
|
|
dungeons: Array.from(this.registry.dungeons.values()),
|
|
enemies: Array.from(this.registry.enemies.values()),
|
|
rooms: Array.from(this.registry.rooms.values()),
|
|
languages: Object.fromEntries(this.registry.languages),
|
|
manifest: this.registry.manifest,
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = new DatapackLoader();
|