@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
381 lines (380 loc) • 14.3 kB
JavaScript
"use strict";
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const Log_1 = __importDefault(require("../core/Log"));
const ste_events_1 = require("ste-events");
const ManagedComponent_1 = require("./ManagedComponent");
const StorageUtilities_1 = __importDefault(require("../storage/StorageUtilities"));
const Database_1 = __importDefault(require("./Database"));
const MinecraftUtilities_1 = __importDefault(require("./MinecraftUtilities"));
const IProjectItemData_1 = require("../app/IProjectItemData");
const AttachableResourceDefinition_1 = __importDefault(require("./AttachableResourceDefinition"));
const TypeScriptDefinition_1 = __importDefault(require("./TypeScriptDefinition"));
const Utilities_1 = __importDefault(require("../core/Utilities"));
const ProjectUtilities_1 = __importDefault(require("../app/ProjectUtilities"));
class ItemTypeDefinition {
_wrapper = null;
_file;
_id;
_isLoaded = false;
_loadedWithComments = false;
_managed = {};
_data;
_onLoaded = new ste_events_1.EventDispatcher();
_onComponentAdded = new ste_events_1.EventDispatcher();
_onComponentRemoved = new ste_events_1.EventDispatcher();
_onComponentChanged = new ste_events_1.EventDispatcher();
get data() {
return this._data;
}
get onComponentAdded() {
return this._onComponentAdded.asEvent();
}
get onComponentRemoved() {
return this._onComponentRemoved.asEvent();
}
get onComponentChanged() {
return this._onComponentChanged.asEvent();
}
get isLoaded() {
return this._isLoaded;
}
get behaviorPackFile() {
return this._file;
}
get onLoaded() {
return this._onLoaded.asEvent();
}
set behaviorPackFile(newFile) {
if (this._file) {
this._file.onFileContentUpdated.unsubscribe(this._handleFileUpdated);
}
this._file = newFile;
if (this._file) {
this._file.onFileContentUpdated.subscribe(this._handleFileUpdated);
}
}
_handleFileUpdated(file, fileB) {
this._data = undefined;
this._isLoaded = false;
this._wrapper = null;
this._managed = {};
}
get id() {
if (this._id === undefined) {
return "";
}
return this._id;
}
set id(newId) {
this._id = newId;
}
get formatVersion() {
if (this._wrapper) {
return this._wrapper.format_version;
}
return undefined;
}
get shortId() {
if (this._id !== undefined) {
if (this._id.startsWith("minecraft:")) {
return this._id.substring(10, this._id.length);
}
return this._id;
}
return undefined;
}
async getFormatVersionIsCurrent() {
const fv = this.getFormatVersion();
if (fv === undefined || fv.length !== 3) {
return false;
}
return await Database_1.default.isRecentVersionFromVersionArray(fv);
}
getFormatVersion() {
if (!this._wrapper) {
return undefined;
}
return MinecraftUtilities_1.default.getVersionArrayFrom(this._wrapper.format_version);
}
setFormatVersion(version) {
if (!this._wrapper) {
return;
}
this._wrapper.format_version = version;
}
constructor() {
this._handleFileUpdated = this._handleFileUpdated.bind(this);
}
ensureComponent(id, defaultData) {
const comp = this.getComponent(id);
if (comp) {
return comp;
}
return this.addComponent(id, defaultData);
}
getComponent(id) {
if (!this._data || !this._data.components) {
return undefined;
}
if (!Utilities_1.default.isUsableAsObjectKey(id)) {
Log_1.default.unsupportedToken(id);
throw new Error();
}
if (!this._managed[id]) {
const comp = this._data.components[id];
if (comp) {
this._managed[id] = new ManagedComponent_1.ManagedComponent(this._data.components, id, comp);
}
}
return this._managed[id];
}
notifyComponentUpdated(id) {
const component = this.getComponent(id);
if (component === undefined) {
Log_1.default.unexpectedUndefined("ITNCU");
}
else {
this._onComponentChanged.dispatch(this, component);
}
}
getAllComponents() {
return this.getComponents();
}
getCustomComponentIds() {
let customComponentIds = [];
const customComponent = this.getComponent("minecraft:custom_components");
if (customComponent) {
let compData = customComponent.getData();
if (compData && Array.isArray(compData)) {
for (const str of compData) {
if (typeof str === "string") {
customComponentIds.push(str);
}
}
}
}
for (const comp of this.getComponents()) {
if (!comp.id.startsWith("minecraft:") && !comp.id.startsWith("tag:")) {
customComponentIds.push(comp.id);
}
}
return customComponentIds;
}
getComponents() {
const componentSet = [];
if (this._data !== undefined) {
for (const componentName in this._data.components) {
const component = this.getComponent(componentName);
if (component !== undefined) {
componentSet.push(component);
}
}
}
return componentSet;
}
async addChildItems(project, item, index) {
let customComponentIds = this.getCustomComponentIds();
// Check TypeScript files for custom components (only if we have custom component IDs)
if (customComponentIds && customComponentIds.length > 0) {
const tsItems = project.getItemsByType(IProjectItemData_1.ProjectItemType.ts);
for (const candItem of tsItems) {
if (!candItem.isContentLoaded) {
await candItem.loadContent();
}
if (candItem.primaryFile) {
if (!candItem.primaryFile.isContentLoaded) {
await candItem.primaryFile.loadContent();
}
const tsd = await TypeScriptDefinition_1.default.ensureOnFile(candItem.primaryFile);
if (tsd && tsd.data) {
let doAddTs = false;
for (const customCompId of customComponentIds) {
if (tsd.data.indexOf(customCompId) >= 0) {
doAddTs = true;
break;
}
}
if (doAddTs) {
item.addChildItem(candItem);
}
}
}
}
}
// Check attachable resources
if (index && this.id) {
// Use pre-built index for O(1) lookup
const matchingAttachables = index.getItemsById(index.attachablesById, this.id);
for (const candItem of matchingAttachables) {
item.addChildItem(candItem);
}
}
else {
const attachableItems = project.getItemsByType(IProjectItemData_1.ProjectItemType.attachableResourceJson);
for (const candItem of attachableItems) {
if (!candItem.isContentLoaded) {
await candItem.loadContent();
}
if (candItem.primaryFile) {
const ard = await AttachableResourceDefinition_1.default.ensureOnFile(candItem.primaryFile);
if (ard) {
const id = ard.id;
if (id === this.id) {
item.addChildItem(candItem);
}
}
}
}
}
}
setBehaviorPackFormatVersion(versionStr) {
this._ensureBehaviorPackDataInitialized();
if (this._wrapper) {
this._wrapper.format_version = versionStr;
}
}
addComponent(id, componentOrData) {
this._ensureBehaviorPackDataInitialized();
const bpData = this._data;
const mc = componentOrData instanceof ManagedComponent_1.ManagedComponent
? componentOrData
: new ManagedComponent_1.ManagedComponent(bpData.components, id, componentOrData);
bpData.components[id] = mc.getData();
this._managed[id] = mc;
this._onComponentAdded.dispatch(this, mc);
return mc;
}
removeComponent(id) {
if (this._data === undefined) {
return;
}
const newBehaviorPacks = {};
const newManagedComponents = {};
for (const name in this._data.components) {
if (name !== id) {
const component = this._data.components[name];
newBehaviorPacks[name] = component;
}
}
for (const name in this._managed) {
if (name !== id) {
newManagedComponents[name] = this._managed[name];
}
}
this._data.components = newBehaviorPacks;
this._managed = newManagedComponents;
}
_ensureBehaviorPackDataInitialized() {
if (this._data === undefined) {
this._data = {
description: {
identifier: "unknown",
},
components: {},
events: {},
};
}
}
async addCustomComponent(itemTypeItem, componentName) {
let componentNameShort = componentName;
const idx = componentName.indexOf(":");
if (idx >= 0) {
componentNameShort = componentName.substring(idx + 1);
}
this.ensureComponent(componentName, {});
const fileNameSugg = Utilities_1.default.getHumanifiedObjectNameNoSpaces(componentNameShort);
this.setFormatVersion("1.21.100");
await ProjectUtilities_1.default.ensureTypeScriptFileWith(itemTypeItem.project, componentName, "new-templates", "itemCustomComponent", fileNameSugg, {
"example:newComponentId": componentName,
ExampleNewComponent: fileNameSugg,
initExampleNew: "init" + fileNameSugg,
});
await ProjectUtilities_1.default.ensureContentInDefaultScriptFile(itemTypeItem.project, "import { init" + fileNameSugg, "import { init" + fileNameSugg + ' } from "./' + fileNameSugg + '"\n', false);
await ProjectUtilities_1.default.ensureContentInDefaultScriptFile(itemTypeItem.project, "init" + fileNameSugg + "()", "init" + fileNameSugg + "();\n", true);
this.persist();
}
static async ensureOnFile(file, loadHandler, preserveComments) {
let itt;
if (file.manager === undefined) {
itt = new ItemTypeDefinition();
itt.behaviorPackFile = file;
file.manager = itt;
}
if (file.manager !== undefined && file.manager instanceof ItemTypeDefinition) {
itt = file.manager;
if (!itt.isLoaded || (preserveComments && !itt._loadedWithComments)) {
if (loadHandler) {
itt.onLoaded.subscribe(loadHandler);
}
await itt.load(preserveComments);
}
}
return itt;
}
static isVisualComponent(value) {
if (value === "minecraft:icon" ||
value === "minecraft:display_name" ||
value === "minecraft:glint" ||
value === "minecraft:hover_text_color") {
return true;
}
return false;
}
persist() {
if (this._file === undefined) {
return false;
}
Log_1.default.assert(!this._isLoaded || this._wrapper !== null, "ITDP");
if (!this._wrapper) {
return false;
}
return this._file.setObjectContentIfSemanticallyDifferent(this._wrapper);
}
/**
* Loads the definition from the file.
* @param preserveComments If true, uses comment-preserving JSON parsing for edit/save cycles.
* If false (default), uses efficient standard JSON parsing.
* Can be called again with true to "upgrade" a read-only load to read/write.
*/
async load(preserveComments = false) {
// If already loaded with comments, we have the "best" version - nothing more to do
if (this._isLoaded && this._loadedWithComments) {
return;
}
// If already loaded without comments and caller doesn't need comments, we're done
if (this._isLoaded && !preserveComments) {
return;
}
if (this._file === undefined) {
return;
}
if (!this._file.isContentLoaded) {
await this._file.loadContent();
}
if (this._file.content === null || this._file.content instanceof Uint8Array) {
this._isLoaded = true;
this._onLoaded.dispatch(this, this);
return;
}
// Use comment-preserving parser only when needed for editing
this._wrapper = preserveComments
? StorageUtilities_1.default.getJsonObjectWithComments(this._file)
: StorageUtilities_1.default.getJsonObject(this._file);
if (this._wrapper) {
const item = this._wrapper["minecraft:item"];
if (item && item.description) {
this.id = item.description.identifier;
}
this._data = item;
}
this._isLoaded = true;
this._loadedWithComments = preserveComments;
this._onLoaded.dispatch(this, this);
}
}
exports.default = ItemTypeDefinition;