@xmcl/resourcepack
Version:
A Minecraft resource pack parser
399 lines (393 loc) • 12 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// index.ts
var resourcepack_exports = {};
__export(resourcepack_exports, {
ModelLoader: () => ModelLoader,
ResourceLocation: () => ResourceLocation,
ResourceManager: () => ResourceManager,
ResourcePack: () => ResourcePack,
readIcon: () => readIcon,
readPackMeta: () => readPackMeta,
readPackMetaAndIcon: () => readPackMetaAndIcon
});
module.exports = __toCommonJS(resourcepack_exports);
var import_system2 = require("@xmcl/system");
// resourceManager.ts
var ResourceManager = class {
constructor(list = []) {
this.list = list;
}
get allResourcePacks() {
return this.list.map((l) => l.info);
}
/**
* Add a new resource source as the first priority of the resource list.
*/
async addResourcePack(resourcePack) {
let info;
try {
info = await resourcePack.info();
} catch {
info = { pack_format: -1, description: "" };
}
const domains = await resourcePack.domains();
const wrapper = { info, source: resourcePack, domains };
this.list.push(wrapper);
return wrapper;
}
remove(index) {
return this.list.splice(index, 1)[0];
}
/**
* Clear all resource packs in this manager
*/
clear() {
return this.list.splice(0, this.list.length);
}
/**
* Swap the resource source priority.
*/
swap(first, second) {
if (first >= this.list.length || first < 0 || second >= this.list.length || second < 0) {
throw new Error("Illegal index");
}
const fir = this.list[first];
this.list[first] = this.list[second];
this.list[second] = fir;
}
/**
* Get the resource in that location. This will walk through current resource source list to load the resource.
* @param location The resource location
*/
async get(location) {
for (let i = this.list.length - 1; i >= 0; i--) {
const src = this.list[i];
const resource = await src.source.get(location);
if (resource) {
return resource;
}
}
return void 0;
}
};
// resourcePack.ts
var import_system = require("@xmcl/system");
var ResourceLocation = class {
constructor(domain, path) {
this.domain = domain;
this.path = path;
}
static deconstruct(path, appendPath = "") {
const splitPath = path.split(":");
const domain = splitPath.length > 1 && splitPath[0] ? splitPath[0] : "minecraft";
let resourcePath = splitPath.length > 1 ? splitPath[1] : splitPath[0];
if (appendPath.length > 0) {
if (appendPath.charAt(appendPath.length - 1) !== "/") {
appendPath = appendPath + "/";
}
if (!resourcePath.startsWith(appendPath)) {
resourcePath = appendPath + resourcePath;
}
}
return new ResourceLocation(domain, resourcePath);
}
/**
* build from texture path
*/
static ofTexturePath(location) {
if (typeof location === "string") {
location = ResourceLocation.deconstruct(location);
}
return new ResourceLocation(location.domain, `textures/${location.path}.png`);
}
/**
* build from model path
*/
static ofBlockModelPath(location) {
location = ResourceLocation.deconstruct(location.toString(), "block/");
return new ResourceLocation(location.domain, `models/${location.path}.json`);
}
static ofItemModelPath(location) {
location = ResourceLocation.deconstruct(location.toString(), "item/");
return new ResourceLocation(location.domain, `models/${location.path}.json`);
}
static ofModelPath(location) {
if (typeof location === "string") {
location = ResourceLocation.deconstruct(location);
}
return new ResourceLocation(location.domain, `models/${location.path}.json`);
}
/**
* build from block state path
*/
static ofBlockStatePath(location) {
if (typeof location === "string") {
location = ResourceLocation.deconstruct(location);
}
return new ResourceLocation(location.domain, `blockstates/${location.path}.json`);
}
/**
* from absoluted path
*/
static fromPath(location) {
return ResourceLocation.deconstruct(location.toString());
}
static getAssetsPath(location) {
if (typeof location === "string") {
location = ResourceLocation.deconstruct(location);
}
return `assets/${location.domain}/${location.path}`;
}
toString() {
return `${this.domain}:${this.path}`;
}
};
var ResourcePack = class {
constructor(fs) {
this.fs = fs;
}
/**
* Load the resource content
* @param location The resource location
* @param type The output type of the resource
*/
async load(location, type) {
const p = this.getPath(location);
if (await this.fs.existsFile(p)) {
return this.fs.readFile(p, type);
}
return void 0;
}
/**
* Load the resource metadata which is localted at <resource-path>.mcmeta
*/
async loadMetadata(location) {
const p = this.getPath(location);
const name = p.substring(0, p.lastIndexOf("."));
const metafileName = name + ".mcmeta";
return await this.fs.existsFile(metafileName) ? JSON.parse((await this.fs.readFile(metafileName, "utf-8")).replace(/^\uFEFF/, "")) : {};
}
/**
* Get the url of the resource location.
* Please notice that this is depended on `FileSystem` implementation of the `getUrl`.
*
* @returns The absolute url like `file://` or `http://` depending on underlaying `FileSystem`.
* @see {@link FileSystem}
*/
getUrl(location) {
const p = this.getPath(location);
return this.fs.getUrl(p);
}
/**
* Get the resource on the resource location.
*
* It can be undefined if there is no resource at that location.
* @param location THe resource location
*/
async get(location) {
if (await this.has(location)) {
return {
location,
url: this.getUrl(location),
read: (encoding) => this.load(location, encoding),
readMetadata: () => this.loadMetadata(location)
};
}
}
/**
* Does the resource pack has the resource
*/
has(location) {
return this.fs.existsFile(this.getPath(location));
}
/**
* The owned domain. You can think about the modids.
*/
async domains() {
const files = await this.fs.listFiles("assets");
const result = [];
for (const f of files) {
if (await this.fs.isDirectory("assets/" + f)) {
result.push(f);
}
}
return result;
}
/**
* The pack info, just like resource pack
*/
async info() {
const { pack } = await this.fs.readFile("pack.mcmeta", "utf-8").then(
(s) => JSON.parse(s.replace(/^\uFEFF/, "")),
() => {
throw new Error("Illegal Resourcepack: Cannot find pack.mcmeta!");
}
);
if (!pack) {
throw new Error("Illegal Resourcepack: pack.mcmeta doesn't contain the pack metadata!");
}
return pack;
}
/**
* The icon of the resource pack
*/
icon() {
return this.fs.readFile("pack.png");
}
getPath(location) {
return `assets/${location.domain}/${location.path}`;
}
static async open(resourcePack) {
return new ResourcePack(await (0, import_system.resolveFileSystem)(resourcePack));
}
};
// modelLoader.ts
var ModelLoader = class {
/**
* @param loader The resource loader
*/
constructor(loader) {
this.loader = loader;
}
static findRealTexturePath(model, variantKey) {
let texturePath = model.textures[variantKey];
while (texturePath.startsWith("#")) {
const next = model.textures[texturePath.substring(1, texturePath.length)];
if (!next) {
return void 0;
}
texturePath = next;
}
return texturePath;
}
/**
* All required texture raw resources
*/
textures = {};
/**
* All the resolved model
*/
models = {};
/**
* Load a model by search its parent. It will throw an error if the model is not found.
*/
async loadModel(modelPath, folder = "block") {
const path = ResourceLocation.deconstruct(modelPath, folder);
const resourceLocation = ResourceLocation.ofModelPath(path);
const cacheName = resourceLocation.toString();
if (this.models[cacheName] !== void 0) {
return this.models[cacheName];
}
const resource = await this.loader.get(resourceLocation);
if (!resource) {
throw new Error(`Model ${modelPath} (${resourceLocation}) not found`);
}
const baseModel = JSON.parse(await resource.read("utf-8"));
if (!baseModel.textures) {
baseModel.textures = {};
}
if (baseModel.parent) {
const parentModel = await this.loadModel(baseModel.parent, "");
if (!parentModel) {
throw new Error(`Missing parent model ${baseModel.parent} for ${resource.location}`);
}
if (!baseModel.elements) {
baseModel.elements = parentModel.elements;
}
if (!baseModel.ambientocclusion) {
baseModel.ambientocclusion = parentModel.ambientocclusion;
}
if (!baseModel.display) {
baseModel.display = parentModel.display;
}
if (!baseModel.overrides) {
baseModel.overrides = parentModel.overrides;
}
if (parentModel.textures) {
Object.assign(baseModel.textures, parentModel.textures);
}
}
baseModel.ambientocclusion = baseModel.ambientocclusion || false;
baseModel.overrides = baseModel.overrides || [];
delete baseModel.parent;
const model = baseModel;
const reg = this.textures;
for (const variant of Object.keys(model.textures)) {
const texPath = ModelLoader.findRealTexturePath(model, variant);
if (texPath) {
const load = await this.loader.get(ResourceLocation.ofTexturePath(texPath));
if (load) {
reg[texPath] = load;
}
}
}
this.models[cacheName] = model;
return model;
}
};
// index.ts
async function readPackMeta(resourcePack) {
const system = await (0, import_system2.resolveFileSystem)(resourcePack);
try {
if (!await system.existsFile("pack.mcmeta")) {
throw new Error("Illegal Resourcepack: Cannot find pack.mcmeta!");
}
const metadata = JSON.parse((await system.readFile("pack.mcmeta", "utf-8")).replace(/^\uFEFF/, ""));
if (!metadata.pack) {
throw new Error("Illegal Resourcepack: pack.mcmeta doesn't contain the pack metadata!");
}
return metadata.pack;
} finally {
if (system !== resourcePack)
system.close();
}
}
async function readIcon(resourcePack) {
const system = await (0, import_system2.resolveFileSystem)(resourcePack);
try {
return system.readFile("pack.png");
} finally {
if (system !== resourcePack)
system.close();
}
}
async function readPackMetaAndIcon(resourcePack) {
const system = await (0, import_system2.resolveFileSystem)(resourcePack);
try {
return {
metadata: await readPackMeta(system),
icon: await readIcon(system).catch(() => void 0)
};
} finally {
if (system !== resourcePack)
system.close();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
ModelLoader,
ResourceLocation,
ResourceManager,
ResourcePack,
readIcon,
readPackMeta,
readPackMetaAndIcon
});
//# sourceMappingURL=index.js.map