UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

1,098 lines (1,097 loc) 43 kB
"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 }); exports.BlockStateType = void 0; const BlockRenderType_1 = require("./BlockRenderType"); const MinecraftUtilities_1 = __importDefault(require("./MinecraftUtilities")); const Database_1 = __importDefault(require("./Database")); const Utilities_1 = __importDefault(require("../core/Utilities")); const ste_events_1 = require("ste-events"); const Log_1 = __importDefault(require("../core/Log")); const ManagedComponent_1 = require("./ManagedComponent"); const StorageUtilities_1 = __importDefault(require("../storage/StorageUtilities")); const ManagedPermutation_1 = __importDefault(require("./ManagedPermutation")); const IProjectItemData_1 = require("../app/IProjectItemData"); const ModelGeometryDefinition_1 = __importDefault(require("./ModelGeometryDefinition")); const BlocksCatalogDefinition_1 = __importDefault(require("./BlocksCatalogDefinition")); const TerrainTextureCatalogDefinition_1 = __importDefault(require("./TerrainTextureCatalogDefinition")); const TypeScriptDefinition_1 = __importDefault(require("./TypeScriptDefinition")); const ProjectUtilities_1 = __importDefault(require("../app/ProjectUtilities")); var BlockStateType; (function (BlockStateType) { BlockStateType[BlockStateType["string"] = 0] = "string"; BlockStateType[BlockStateType["boolean"] = 1] = "boolean"; BlockStateType[BlockStateType["number"] = 2] = "number"; })(BlockStateType || (exports.BlockStateType = BlockStateType = {})); class BlockTypeDefinition { _typeId = ""; _typeData; _baseType; _baseTypeId = ""; _material = ""; _isCustom = false; _wrapper = null; _file; _id; _isLoaded = false; _loadedWithComments = false; _data; _managed = {}; _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 numericId() { return this._typeData.lid; } get baseTypeId() { return this._baseTypeId; } get mapColor() { return this._typeData.mc; } get isCustom() { return this._isCustom; } get baseType() { if (this._baseType !== undefined) { return this._baseType; } return Database_1.default.defaultBlockBaseType; } /** * Returns true if this block uses a unit cube geometry. * Unit cube blocks can be rendered with textures from blocks.json or material_instances. * * A block is considered a unit cube if: * 1. It has minecraft:unit_cube component (legacy) * 2. It has geometry = "minecraft:geometry.full_block" or "geometry.full_block" * 3. It has no geometry component at all (defaults to unit cube) */ get isUnitCube() { // Check for explicit minecraft:unit_cube component (legacy) const unitCubeComponent = this.getComponent("minecraft:unit_cube"); if (unitCubeComponent) { return true; } const geoId = this.geometryIdentifier; // No geometry = unit cube if (!geoId) { return true; } // Explicit full_block geometry if (geoId === "minecraft:geometry.full_block" || geoId === "geometry.full_block") { return true; } return false; } get geometryIdentifier() { // First check base components const geoComponent = this.getComponent("minecraft:geometry"); if (geoComponent) { // Handle both legacy and modern formats: // Legacy (1.16.x): "minecraft:geometry": "geometry.barrier_slab" (string directly) // Modern (1.19.40+): "minecraft:geometry": { "identifier": "geometry.my_block", ... } const data = geoComponent.getData(); if (typeof data === "string") { return data; } const id = geoComponent.getProperty("identifier"); if (id) { return id; } } // If no geometry in base components, check permutations // This handles blocks where geometry is only defined in permutations (e.g., arc:flask) const geometryList = this.getGeometryList(); if (geometryList && geometryList.length > 0) { return geometryList[0]; } return undefined; } /** * Gets the blocks.json catalog resource for this block, if it exists. * Returns the IBlockResource which contains texture references for unit cube blocks. */ async getBlockCatalogResource(project) { const blockCatalog = await BlocksCatalogDefinition_1.default.getBlockCatalog(project); if (blockCatalog && this.id) { return blockCatalog.getCatalogResource(this.id); } return undefined; } async setBlockCatalogTexture(project, sideId, textureId) { if (this.geometryIdentifier === "minecraft:geometry.full_block") { const blockCatalog = await BlocksCatalogDefinition_1.default.ensureBlockCatalog(project); if (blockCatalog) { const blockResource = blockCatalog?.ensureCatalogResource(this.id); if (blockResource.textures && typeof blockResource.textures !== "string") { blockResource.textures[sideId] = textureId; } blockCatalog.persist(); } } } async setTerrainTexture(project, terrainTextureId, textureData) { if (this.geometryIdentifier === "minecraft:geometry.full_block") { const terrainTextureCatalog = await TerrainTextureCatalogDefinition_1.default.ensureTerrainTextureCatalog(project); if (terrainTextureCatalog) { terrainTextureCatalog.setTexture(terrainTextureId, textureData); terrainTextureCatalog.persist(); } } } async ensureBlockAndTerrainLinks(project, creationData) { if (this.geometryIdentifier === "minecraft:geometry.full_block") { const blockCatalog = await BlocksCatalogDefinition_1.default.ensureBlockCatalog(project); const terrainTextureCatalog = await TerrainTextureCatalogDefinition_1.default.ensureTerrainTextureCatalog(project); if (creationData && creationData.resource && blockCatalog) { blockCatalog?.setBlockDefinition(this.id, creationData.resource); } if (creationData && creationData.texture_data && terrainTextureCatalog) { for (const textureId in creationData.texture_data) { const textureData = creationData.texture_data[textureId]; terrainTextureCatalog.setTexture(textureId, textureData); } const carriedTexturesId = creationData.resource.carried_textures; if (carriedTexturesId) { const textureData = creationData.texture_data[carriedTexturesId]; terrainTextureCatalog.setTexture(carriedTexturesId, textureData); } } } } async getFormatVersionIsCurrent() { const fv = this.getFormatVersion(); if (fv === undefined || fv.length !== 3) { return false; } return await Database_1.default.isRecentVersionFromVersionArray(fv); } getMissingPermutations() { this.ensurePermutations(); const unspecifiedConditions = []; for (const state of this.getExpandedStateList()) { let isUsedInConditionalExpression = false; for (const perm of this.getPermutations()) { if (perm.condition.indexOf(state) >= 0 && (perm.condition.indexOf("!=") >= 0 || perm.condition.indexOf(">=") >= 0 || perm.condition.indexOf("<=") >= 0)) { isUsedInConditionalExpression = true; break; } } if (!isUsedInConditionalExpression) { unspecifiedConditions.push(state); } } const stateIds = []; const vals = []; for (const state of unspecifiedConditions) { const stateValues = this.getStateValues(state); if (stateValues) { vals.push(stateValues); stateIds.push(state); } } let stateList = []; let idx = 0; for (const valArrs of vals) { let newStateList = []; for (let i = 0; i < valArrs.length; i++) { let strVal = valArrs[i]; if (typeof strVal === "string") { strVal = "'" + strVal + "'"; } if (stateList.length === 0) { let condition = "q.block_state('" + stateIds[idx] + "') == " + strVal; if (!this.getPermutationByCondition(condition)) { newStateList.push(condition); } } else { for (let j = 0; j < stateList.length; j++) { let condition = stateList[j] && " && q.block_state('" + stateIds[idx] + "') == " + strVal; if (!this.getPermutationByCondition(condition)) { newStateList.push(stateList[j] && " && q.block_state('" + stateIds[idx] + "') == " + strVal); } } } } idx++; stateList = newStateList; } return stateList; } addNextPermutation() { this.ensurePermutations(); let missingConditions = this.getMissingPermutations(); const cond = missingConditions.length > 0 ? missingConditions[0] : "q.block_state() == ''"; this.addPermutation(cond); } addPermutation(condition) { this.ensurePermutations(); if (!this._data || !this._data.permutations) { return; } this._data.permutations.push({ condition: condition, components: {}, }); } ensurePermutations() { if (!this._data) { this._data = { description: { identifier: this._typeId, }, components: {}, permutations: [], events: {}, }; } if (!this._data.description) { this._data.description = { identifier: this._typeId, }; } if (!this._data.permutations) { this._data.permutations = []; } } hasCustomPermutationConditions() { if (!this._data || !this._data.permutations || !Array.isArray(this._data.permutations)) { return false; } for (const perm of this._data.permutations) { if (perm.condition && (perm.condition.indexOf(">=") >= 0 || perm.condition.indexOf("<=") >= 0 || perm.condition.indexOf("!=") >= 0)) { return true; } } return false; } ensureDescription() { if (!this._data) { this._data = { description: { identifier: this._typeId, }, components: {}, events: {}, }; } if (!this._data.description) { this._data.description = { identifier: this._typeId, }; } } getManagedPermutations() { const permData = this.getPermutations(); if (!permData || !Array.isArray(permData)) { return undefined; } const managedPerms = []; for (const permDataItem of permData) { managedPerms.push(new ManagedPermutation_1.default(permDataItem)); } return managedPerms; } getPermutations() { if (!this._data || !this.data?.permutations) { return []; } return this.data.permutations; } getPlacementDirectionTrait() { if (!this._data || !this._data.description || !this._data.description.traits) { return undefined; } const traits = this._data?.description.traits; return traits["minecraft:placement_direction"]; } getPlacementPositionTrait() { if (!this._data || !this._data.description || !this._data.description.traits) { return undefined; } const traits = this._data?.description.traits; return traits["minecraft:placement_position"]; } ensurePlacementDirectionTrait() { this.ensureBlockTraits(); const traits = this._data?.description.traits; if (traits) { if (!traits["minecraft:placement_direction"]) { traits["minecraft:placement_direction"] = { enabled_states: ["minecraft:cardinal_direction", "minecraft:facing_direction"], }; } } } removePlacementDirectionTrait() { const traits = this._data?.description.traits; if (traits) { if (traits["minecraft:placement_direction"]) { traits["minecraft:placement_direction"] = undefined; } } } ensurePlacementPositionTrait() { this.ensureBlockTraits(); const traits = this._data?.description.traits; if (traits) { if (!traits["minecraft:placement_position"]) { traits["minecraft:placement_position"] = { enabled_states: ["minecraft:block_face", "minecraft:vertical_half"], }; } } } removePlacementPositionTrait() { const traits = this._data?.description.traits; if (traits) { if (traits["minecraft:placement_position"]) { traits["minecraft:placement_position"] = undefined; } } } ensureBlockTraits() { this.ensureDescription(); if (this._data?.description && !this._data?.description.traits) { this._data.description.traits = {}; } } removeState(stateName) { if (this._data?.description?.states) { this._data.description.states[stateName] = undefined; } if (this._data?.description?.properties) { this._data.description.properties[stateName] = undefined; } } get formatVersion() { return this._wrapper?.format_version; } getFormatVersion() { if (!this._wrapper) { return undefined; } return MinecraftUtilities_1.default.getVersionArrayFrom(this._wrapper.format_version); } getStateValues(stateId) { if (stateId === "minecraft:block_face" || stateId === "minecraft:facing_direction") { return ["north", "south", "east", "west", "up", "down"]; } else if (stateId === "minecraft:vertical_half") { return ["bottom", "top"]; } else if (stateId === "minecraft:cardinal_direction") { return ["north", "south", "east", "west"]; } const states = this.getStates(); if (!states || !states[stateId]) { return undefined; } return states[stateId]; } getStates() { if (!this._wrapper || !this._data || !this._data?.description) { return undefined; } if (!this._data.description.states && this._data.description.properties) { return this._data.description.properties; } return this._data.description.states; } addState(stateName, stateType) { if (!this._data || !this._data.description) { return; } if (!Utilities_1.default.isUsableAsObjectKey(stateName)) { Log_1.default.unsupportedToken(stateName); throw new Error(); } let dataArr = []; if (stateType === BlockStateType.boolean) { dataArr = [false, true]; } else if (stateType === BlockStateType.number) { dataArr = [0, 1, 2]; } else if (stateType === BlockStateType.string) { dataArr = ["value1", "value2"]; } if (!this._data.description.states) { this._data.description.states = {}; } this._data.description.states[stateName] = dataArr; } getExpandedStateList() { const stateList = this.getStateList(); let placementDir = this.getPlacementDirectionTrait(); if (placementDir) { if (placementDir.enabled_states) { stateList.push(...placementDir.enabled_states); } } let placementPos = this.getPlacementPositionTrait(); if (placementPos) { if (placementPos.enabled_states) { stateList.push(...placementPos.enabled_states); } } return stateList; } getStateList() { const states = this.getStates(); if (!states) { return []; } const stateList = []; for (const state in states) { if (states[state] !== undefined) { stateList.push(state); } } return stateList; } set baseType(baseType) { this._baseType = baseType; this._baseTypeId = baseType.name; } get material() { return this._material; } renderType = BlockRenderType_1.BlockRenderType.Custom; get icon() { let val = this._typeData.ic; if (val === undefined && this.baseType !== undefined) { val = this.baseType.icon; } return val; } get typeId() { return this._typeId; } get shortTypeName() { let name = this._typeId; const colonIndex = name.indexOf(":"); if (colonIndex >= 0) { name = name.substring(colonIndex + 1, name.length); } return name; } get title() { const id = this.shortTypeName; return Utilities_1.default.humanifyMinecraftName(id); } constructor(name) { this._typeId = name; this._handleFileUpdated = this._handleFileUpdated.bind(this); this._typeData = { n: name, }; if (name.indexOf(":") >= 0 && !name.startsWith("minecraft:")) { this._isCustom = true; } } get id() { if (this._data && this._data.description) { return this._data.description.identifier; } if (!this._id) { return ""; } return this._id; } set id(newId) { this._id = newId; if (this._data && this._data.description && newId) { this._data.description.identifier = newId; } } 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._wrapper = null; this._isLoaded = false; this._managed = {}; } get shortId() { if (this._id !== undefined) { if (this._id.startsWith("minecraft:")) { return this._id.substring(10, this._id.length); } return this._id; } return undefined; } getPermutationByCondition(permutationCondition) { if (!this._data || !this.data?.permutations || !Array.isArray(this.data.permutations)) { return undefined; } for (const perm of this.data.permutations) { if (permutationCondition === perm.condition) { return perm; } } return undefined; } static getComponentFromBaseFileName(name) { let canonName = name; if (canonName.startsWith("minecraft_")) { canonName = canonName.substring(10); } return canonName; } ensureComponent(id, defaultData) { const comp = this.getComponent(id); if (comp) { return comp; } return this.addComponent(id, defaultData); } getComponent(id) { if (this._data === undefined) { return undefined; } if (!this._managed[id]) { if (this._data.components) { const comp = this._data.components[id]; if (comp) { this._managed[id] = new ManagedComponent_1.ManagedComponent(this._data.components, id, comp); } } } return this._managed[id]; } getComponentsInBaseAndPermutations(id) { if (this._data === undefined) { return []; } let results = []; let comp = this.getComponent(id); if (comp) { results.push(comp); } const perms = this.getManagedPermutations(); if (perms && Array.isArray(perms)) { for (const perm of perms) { if (perm) { comp = perm.getComponent(id); if (comp) { results.push(comp); } } } } return results; } notifyComponentUpdated(id) { const component = this.getComponent(id); if (component === undefined) { Log_1.default.unexpectedUndefined("BTNCU"); } else { this._onComponentChanged.dispatch(this, component); } } getAllComponents() { return this.getComponents(); } 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; } 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 newComponents = {}; for (const name in this._data.components) { if (name !== id) { if (Utilities_1.default.isUsableAsObjectKey(name)) { const component = this._data.components[name]; newBehaviorPacks[name] = component; } } } for (const name in this._managed) { if (name !== id) { newComponents[name] = this._managed[name]; } } this._data.components = newBehaviorPacks; this._managed = newComponents; } async getTextureItems(blockTypeProjectItem, project) { if (!this._data || !blockTypeProjectItem.childItems) { return undefined; } let textureList = this.getTextureList(); // For unit cube blocks, also include textures from blocks.json if (this.isUnitCube && project) { const catalogTextures = await this.getTextureListFromBlocksCatalog(project); if (catalogTextures && catalogTextures.length > 0) { textureList = textureList ? [...textureList, ...catalogTextures] : catalogTextures; } } const results = {}; for (const childItem of blockTypeProjectItem.childItems) { let candItem = childItem.childItem; if (candItem.itemType === IProjectItemData_1.ProjectItemType.terrainTextureCatalogResourceJson) { if (!candItem.isContentLoaded) { await candItem.loadContent(); } // Ensure the terrain texture catalog's dependencies are loaded (links to texture files) await candItem.ensureDependencies(); if (candItem.primaryFile && candItem.childItems) { const blockTextureCatalog = await TerrainTextureCatalogDefinition_1.default.ensureOnFile(candItem.primaryFile); if (blockTextureCatalog && textureList) { for (const textureId of textureList) { const texPaths = blockTextureCatalog.getAllTexturePaths(textureId); if (texPaths) { for (const texPath of texPaths) { const texPathLower = texPath.toLowerCase(); for (const catalogChildItem of candItem.childItems) { let path = catalogChildItem.childItem.projectPath; if (path) { const lastPeriod = path.lastIndexOf("."); if (lastPeriod >= 0) { path = path.substring(0, lastPeriod); } // Case-insensitive matching since terrain_texture paths may have different casing const pathLower = path.toLowerCase(); if (pathLower.endsWith(texPathLower)) { results[texPath] = catalogChildItem.childItem; } } } } } } } } } } return results; } getGeometryList() { if (!this._data) { return undefined; } const comps = this.getComponentsInBaseAndPermutations("minecraft:geometry"); if (!comps) { return undefined; } const geometryList = []; for (const comp of comps) { const compData = comp.getData(); if (typeof compData === "string") { geometryList.push(compData); } else { const id = comp.getProperty("identifier"); if (id) { geometryList.push(id); } } } return geometryList; } /** * Gets the list of texture IDs used by this block. * Checks both material_instances component and blocks.json catalog. */ getTextureList() { if (!this._data) { return undefined; } const textureList = []; // Check material_instances component const comps = this.getComponentsInBaseAndPermutations("minecraft:material_instances"); if (comps) { for (const comp of comps) { const compData = comp.getData(); if (typeof compData === "object") { for (const materialName in compData) { const material = compData[materialName]; if (material && material.texture) { textureList.push(material.texture); } } } } } return textureList; } /** * Gets texture IDs from blocks.json catalog for this block. * Used for unit cube blocks that define textures via blocks.json. */ async getTextureListFromBlocksCatalog(project) { const textureList = []; const blockResource = await this.getBlockCatalogResource(project); if (blockResource && blockResource.textures) { if (typeof blockResource.textures === "string") { textureList.push(blockResource.textures); } else { // IBlockTextures object with per-face textures const textures = blockResource.textures; if (textures.north) textureList.push(textures.north); if (textures.south) textureList.push(textures.south); if (textures.east) textureList.push(textures.east); if (textures.west) textureList.push(textures.west); if (textures.up) textureList.push(textures.up); if (textures.down) textureList.push(textures.down); if (textures.side) textureList.push(textures.side); } // Also check carried_textures if (blockResource.carried_textures) { textureList.push(blockResource.carried_textures); } } // Deduplicate return [...new Set(textureList)]; } _ensureBehaviorPackDataInitialized() { if (this._data === undefined) { this._data = { description: { identifier: "unknown", }, components: {}, events: {}, }; } } getPackRootFolder() { let packRootFolder = undefined; if (this._file && this._file.parentFolder) { let parentFolder = this._file.parentFolder; packRootFolder = StorageUtilities_1.default.getParentOfParentFolderNamed("blocks", parentFolder); } return packRootFolder; } getCustomComponentIds() { let customComponentIds = []; const customComponents = this.getComponentsInBaseAndPermutations("minecraft:custom_components"); for (const comp of customComponents) { let compData = comp.getData(); if (compData && Array.isArray(compData)) { for (const str of compData) { if (typeof str === "string") { customComponentIds.push(str); } } } } const allComponentsInUse = this.getAllComponents(); for (const component of allComponentsInUse) { if (component && component.id && !component.id.startsWith("minecraft:") && !component.id.startsWith("tag:")) { customComponentIds.push(component.id); } } return customComponentIds; } getLootTablePaths() { let lootTablePaths = []; const lootComps = this.getComponentsInBaseAndPermutations("minecraft:loot"); for (const comp of lootComps) { let compData = comp.getData(); if (typeof compData === "string") { lootTablePaths.push(compData); } else { let lootTablePath = comp.getProperty("table"); if (lootTablePath) { lootTablePaths.push(lootTablePath); } } } return lootTablePaths; } async addChildItems(project, item, index) { let lootTablePaths = this.getLootTablePaths(); let customComponentIds = this.getCustomComponentIds(); let textureList = this.getTextureList(); // For unit cube blocks, also get textures from blocks.json if (this.isUnitCube) { const catalogTextures = await this.getTextureListFromBlocksCatalog(project); if (catalogTextures && catalogTextures.length > 0) { textureList = textureList ? [...textureList, ...catalogTextures] : catalogTextures; } } let geometryList = this.getGeometryList(); // 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 terrain texture catalog (only if we have textures to match) if (textureList && textureList.length > 0) { const terrainTexItems = project.getItemsByType(IProjectItemData_1.ProjectItemType.terrainTextureCatalogResourceJson); for (const candItem of terrainTexItems) { if (!candItem.isContentLoaded) { await candItem.loadContent(); } if (candItem.primaryFile) { const blockTextureCatalog = await TerrainTextureCatalogDefinition_1.default.ensureOnFile(candItem.primaryFile); if (blockTextureCatalog) { let doAddTextureCatalog = false; for (const textureId of textureList) { const blockResource = blockTextureCatalog.getTexture(textureId); if (blockResource) { doAddTextureCatalog = true; break; } } if (doAddTextureCatalog) { item.addChildItem(candItem); } } } } } // Check blocks catalog const blocksCatalogItems = project.getItemsByType(IProjectItemData_1.ProjectItemType.blocksCatalogResourceJson); for (const candItem of blocksCatalogItems) { if (!candItem.isContentLoaded) { await candItem.loadContent(); } if (candItem.primaryFile) { const blockCatalog = await BlocksCatalogDefinition_1.default.ensureOnFile(candItem.primaryFile); if (blockCatalog && this.id) { const blockResource = blockCatalog.getCatalogResource(this.id); if (blockResource) { item.addChildItem(candItem); } } } } // Check model geometry (only if we have geometries to match) if (geometryList && geometryList.length > 0) { if (index) { // Use pre-built index for O(1) model lookups const addedItems = new Set(); for (const geoId of geometryList) { const matchingItems = index.getItemsById(index.modelsById, geoId); for (const candItem of matchingItems) { if (!addedItems.has(candItem)) { addedItems.add(candItem); item.addChildItem(candItem); geometryList = Utilities_1.default.removeItemInArray(geoId, geometryList); } } } } else { const modelItems = project.getItemsByType(IProjectItemData_1.ProjectItemType.modelGeometryJson); for (const candItem of modelItems) { if (!candItem.isContentLoaded) { await candItem.loadContent(); } if (candItem.primaryFile) { const model = await ModelGeometryDefinition_1.default.ensureOnFile(candItem.primaryFile); if (model) { let doAddModel = false; for (const modelId of model.identifiers) { if (geometryList && geometryList.includes(modelId)) { doAddModel = true; geometryList = Utilities_1.default.removeItemInArray(modelId, geometryList); } } if (doAddModel) { item.addChildItem(candItem); } } } } } } // Check loot tables (only if we have loot references) if (lootTablePaths.length > 0) { const lootTableItems = project.getItemsByType(IProjectItemData_1.ProjectItemType.lootTableBehavior); for (const candItem of lootTableItems) { for (const lootTablePath of lootTablePaths) { if (candItem.projectPath?.endsWith(lootTablePath)) { item.addChildItem(candItem); } } } } } static async ensureOnFile(file, loadHandler, preserveComments) { let bt; if (file.manager === undefined) { bt = new BlockTypeDefinition("custom:" + file.name); bt.behaviorPackFile = file; file.manager = bt; } if (file.manager !== undefined && file.manager instanceof BlockTypeDefinition) { bt = file.manager; if (!bt.isLoaded || (preserveComments && !bt._loadedWithComments)) { if (loadHandler) { bt.onLoaded.subscribe(loadHandler); } await bt.load(preserveComments); } } return bt; } async addCustomComponent(blockTypeItem, 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); await ProjectUtilities_1.default.ensureTypeScriptFileWith(blockTypeItem.project, componentName, "new-templates", "blockCustomComponent", fileNameSugg, { "example:newComponentId": componentName, ExampleNewComponent: fileNameSugg, initExampleNew: "init" + fileNameSugg, }); await ProjectUtilities_1.default.ensureContentInDefaultScriptFile(blockTypeItem.project, "import { init" + fileNameSugg, "import { init" + fileNameSugg + ' } from "./' + fileNameSugg + '"\n', false); await ProjectUtilities_1.default.ensureContentInDefaultScriptFile(blockTypeItem.project, "init" + fileNameSugg + "()", "init" + fileNameSugg + "();\n", true); this.persist(); } persist() { if (this._file === undefined) { return false; } Log_1.default.assert(!this._isLoaded || this._wrapper !== null, "BTP"); 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 || this._file.content instanceof Uint8Array) { this._isLoaded = true; this._onLoaded.dispatch(this, this); return; } let data = {}; // Use comment-preserving parser only when needed for editing let result = preserveComments ? StorageUtilities_1.default.getJsonObjectWithComments(this._file) : StorageUtilities_1.default.getJsonObject(this._file); if (result) { data = result; } this._wrapper = data; const block = data["minecraft:block"]; if (block && block.description) { this.id = block.description.identifier; } this._data = block; this._onLoaded.dispatch(this, this); this._isLoaded = true; this._loadedWithComments = preserveComments; } } exports.default = BlockTypeDefinition;