UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

788 lines (787 loc) 37.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const Database_1 = __importDefault(require("./Database")); const ItemTextureCatalogDefinition_1 = __importDefault(require("./ItemTextureCatalogDefinition")); const SoundDefinitionCatalogDefinition_1 = __importDefault(require("./SoundDefinitionCatalogDefinition")); const BlocksCatalogDefinition_1 = __importDefault(require("./BlocksCatalogDefinition")); const TerrainTextureCatalogDefinition_1 = __importDefault(require("./TerrainTextureCatalogDefinition")); const EntityTypeResourceDefinition_1 = __importDefault(require("./EntityTypeResourceDefinition")); const AttachableResourceDefinition_1 = __importDefault(require("./AttachableResourceDefinition")); const ModelGeometryDefinition_1 = __importDefault(require("./ModelGeometryDefinition")); const Log_1 = __importDefault(require("../core/Log")); const VanillaGeometryTransforms_1 = require("./VanillaGeometryTransforms"); const CreatorToolsHost_1 = __importDefault(require("../app/CreatorToolsHost")); const RenderControllerSetDefinition_1 = __importDefault(require("./RenderControllerSetDefinition")); const RenderControllerResolver_1 = __importDefault(require("./RenderControllerResolver")); const IMolangContext_1 = require("./IMolangContext"); class VanillaProjectManager { static blocksCatalog = null; static itemTextureCatalog = null; static terrainTextureCatalog = null; static soundDefinitionCatalog = null; // Cache for entity resource definitions static _entityResourceCache = new Map(); static _entityModelDataCache = new Map(); // Cache for attachable resource definitions static _attachableResourceCache = new Map(); static _attachableModelDataCache = new Map(); /** * Get a list of all vanilla entity type IDs */ static async getVanillaEntityTypeIds() { const vanillaFolder = await Database_1.default.getPreviewVanillaFolder(); if (!vanillaFolder) return []; const entityFolder = await vanillaFolder.getFolderFromRelativePath("/resource_pack/entity/"); if (!entityFolder) return []; await entityFolder.load(); const entityIds = []; for (const fileName in entityFolder.files) { if (fileName.endsWith(".entity.json") && !fileName.includes("v1.0") && !fileName.includes(".v2.") && !fileName.includes(".v3.") && !fileName.includes("_v1.") && !fileName.includes("_v2.") && !fileName.includes("_v3.")) { const entityId = fileName.replace(".entity.json", ""); entityIds.push(entityId); } } entityIds.sort(); return entityIds; } /** * Get entity resource definition for a vanilla entity by type ID */ static async getVanillaEntityResource(typeId) { // Remove minecraft: prefix if present const shortId = typeId.replace("minecraft:", ""); // Check cache first if (this._entityResourceCache.has(shortId)) { return this._entityResourceCache.get(shortId) || null; } const file = await Database_1.default.getPreviewVanillaFile(`/resource_pack/entity/${shortId}.entity.json`); if (!file) return null; const etrd = await EntityTypeResourceDefinition_1.default.ensureOnFile(file); if (etrd) { this._entityResourceCache.set(shortId, etrd); return etrd; } return null; } /** * Get complete model data for a vanilla entity, including geometry and texture. * Uses the "default" variant by default, ensuring geometry and texture are properly matched. * @param typeId - Entity type ID (e.g., "cow" or "minecraft:cow") * @param variantKey - Optional variant key (e.g., "default", "warm", "cold"). Defaults to "default". */ static async getVanillaEntityModelData(typeId, variantKey = "default") { const shortId = typeId.replace("minecraft:", ""); const cacheKey = `${shortId}_${variantKey}`; // Check cache first if (this._entityModelDataCache.has(cacheKey)) { const cached = this._entityModelDataCache.get(cacheKey) || null; // If cached but missing texture, try reloading if (cached && !cached.textureData && !cached.textureUrl) { this._entityModelDataCache.delete(cacheKey); } else { return cached; } } const entityResource = await this.getVanillaEntityResource(shortId); if (!entityResource) { // Not a vanilla entity, return null - this is expected for custom entities return null; } const modelData = { entityTypeId: shortId, }; // Try render controller-based resolution first let rcResolved = null; try { rcResolved = await this._resolveViaRenderController(shortId, entityResource); } catch { // Render controller resolution is optional — fall back to key-based matching } // Get geometry and texture — prefer render controller resolution, fall back to key-based matching let geometryId; let texturePath; if (rcResolved) { geometryId = rcResolved.geometryId; if (rcResolved.textureLayers.length > 0) { texturePath = rcResolved.textureLayers[0].texturePath; // Propagate tint color from first layer if present if (rcResolved.textureLayers[0].tintColor) { modelData.tintColor = rcResolved.textureLayers[0].tintColor; } } } // Fall back to variant-key matching if render controller didn't resolve if (!geometryId || !texturePath) { const matched = entityResource.getMatchedGeometryAndTexture(variantKey); if (!geometryId) geometryId = matched.geometryId; if (!texturePath) texturePath = matched.texturePath; } // Per-material rendering adjustments. // Most vanilla entity materials use alpha TEST (binary: alpha > 0.5 = opaque, // alpha ≤ 0.5 = invisible). This is set in ModelMeshFactory via MATERIAL_ALPHATEST. // A few specific materials render fully OPAQUE (ignoring alpha entirely): const materials = entityResource.data?.materials; const defaultMaterial = materials?.["default"]; const opaqueMaterials = new Set([ "enderman", // Head texture has alpha=0 pixels that should render as opaque black "sheep", // Wool overlay texture has near-zero alpha body pixels ]); if (defaultMaterial && opaqueMaterials.has(defaultMaterial)) { modelData.ignoreAlpha = true; } // Sheep-specific: apply wool tint color if (defaultMaterial === "sheep") { // White sheep wool color from Minecraft's color palette (#E7E7E7 / 0.906 per channel) modelData.tintColor = { r: 0.906, g: 0.906, b: 0.906, a: 1.0 }; } // Load geometry if (geometryId) { modelData.geometryId = geometryId; const geometry = await this._loadVanillaGeometry(geometryId); if (geometry) { // Apply VanillaGeometryTransforms to correct for Minecraft's hardcoded renderer quirks // (e.g., cow and sheep body bone rotation corrections) modelData.geometry = (0, VanillaGeometryTransforms_1.applyGeometryTransforms)(geometry.geometry, geometryId); modelData.modelDefinition = geometry.definition; } else { Log_1.default.debugAlert(`Failed to load geometry for ${shortId}: ${geometryId}`); } } // Load texture - use the matched texture path to ensure it corresponds to the geometry if (texturePath) { // Remove file extension if present if (texturePath.endsWith(".png") || texturePath.endsWith(".tga")) { texturePath = texturePath.substring(0, texturePath.lastIndexOf(".")); } modelData.texturePath = texturePath; // Set texture URL - use serve folder which has PNG versions // The URL is relative to the public folder and will be served at runtime modelData.textureUrl = CreatorToolsHost_1.default.contentWebRoot + `res/latest/van/serve/resource_pack/${texturePath}.png`; // Also try to load texture data for entities that may have it available const textureData = await this.loadVanillaTexture(texturePath); if (textureData) { modelData.textureData = textureData; } } // Cache the result this._entityModelDataCache.set(cacheKey, modelData); return modelData; } /** * Attempt to resolve an entity's geometry and texture via its render controller. * Falls back to null if no render controller is found or resolution fails. */ static async _resolveViaRenderController(shortId, entityResource) { try { const rcIds = entityResource.renderControllerIdList; if (!rcIds || rcIds.length === 0) return null; const textureMap = entityResource.data?.textures; const geometryMap = entityResource.data?.geometry; if (!textureMap || !geometryMap) return null; const rcId = typeof rcIds[0] === "string" ? rcIds[0] : String(rcIds[0]); const rcDef = await this._loadVanillaRenderController(shortId, rcId); if (!rcDef) return null; const resolver = new RenderControllerResolver_1.default(); const context = (0, IMolangContext_1.createDefaultEntityContext)(); const result = resolver.resolve(rcDef, textureMap, geometryMap, context); // For sheep, the render controller resolves to the woolly default geometry // (geometry.sheep.v1.8). The geometry transform system now handles the // body bind_pose_rotation → per-cube rotation conversion, so we no longer // need to force the "sheared" geometry. Let the render controller resolution // stand as-is. return result; } catch (err) { Log_1.default.verbose(`Render controller resolution failed for ${shortId}: ${err}`); return null; } } /** * Load a vanilla render controller by its ID (e.g., "controller.render.sheep.v2"). * Tries candidate filenames based on entity short ID via Database.getPreviewVanillaFile, * which uses the IFile/IFolder storage abstractions and works across web, Node.js, etc. */ static async _loadVanillaRenderController(entityShortId, controllerId) { const candidates = [ `/resource_pack/render_controllers/${entityShortId}.render_controllers.json`, `/resource_pack/render_controllers/${entityShortId}.v2.render_controllers.json`, `/resource_pack/render_controllers/${entityShortId}.v3.render_controllers.json`, `/resource_pack/render_controllers/${entityShortId}.v4.render_controllers.json`, ]; for (const filePath of candidates) { const file = await Database_1.default.getPreviewVanillaFile(filePath); if (file) { const rcSetDef = new RenderControllerSetDefinition_1.default(); rcSetDef.file = file; await rcSetDef.load(); const rcData = rcSetDef.data; if (rcData?.render_controllers) { const rc = rcData.render_controllers[controllerId]; if (rc) return rc; } } } return null; } /** * Get a list of all vanilla attachable type IDs (items with 3D models like armor, bow, shield). * Filters out .player.json variants and index.json. */ static async getVanillaAttachableTypeIds() { const vanillaFolder = await Database_1.default.getPreviewVanillaFolder(); if (!vanillaFolder) return []; const attachableFolder = await vanillaFolder.getFolderFromRelativePath("/resource_pack/attachables/"); if (!attachableFolder) return []; await attachableFolder.load(); const attachableIds = []; for (const fileName in attachableFolder.files) { // Skip player variants (e.g., diamond_chestplate.player.json) and index if (fileName.includes(".player.") || fileName === "index.json") { continue; } if (fileName.endsWith(".json")) { // Remove extensions: "bow.json" → "bow", "crossbow.entity.json" → "crossbow" let id = fileName.replace(".entity.json", "").replace(".json", ""); attachableIds.push(id); } } attachableIds.sort(); return attachableIds; } /** * Get attachable resource definition for a vanilla attachable by type ID. */ static async getVanillaAttachableResource(typeId) { const shortId = typeId.replace("minecraft:", ""); if (this._attachableResourceCache.has(shortId)) { return this._attachableResourceCache.get(shortId) || null; } // Try multiple file name patterns const possibleFiles = [`${shortId}.json`, `${shortId}.entity.json`]; for (const fileName of possibleFiles) { const file = await Database_1.default.getPreviewVanillaFile(`/resource_pack/attachables/${fileName}`); if (file) { const ard = await AttachableResourceDefinition_1.default.ensureOnFile(file); if (ard) { this._attachableResourceCache.set(shortId, ard); return ard; } } } return null; } /** * Get complete model data for a vanilla attachable, including geometry and texture. * @param typeId - Attachable type ID (e.g., "diamond_chestplate" or "minecraft:diamond_chestplate") */ static async getVanillaAttachableModelData(typeId) { const shortId = typeId.replace("minecraft:", ""); if (this._attachableModelDataCache.has(shortId)) { return this._attachableModelDataCache.get(shortId) || null; } const attachableResource = await this.getVanillaAttachableResource(shortId); if (!attachableResource) { return null; } const modelData = { attachableTypeId: shortId, }; // Get default geometry const geometry = attachableResource.geometry; if (geometry && geometry["default"]) { let geometryId = geometry["default"]; modelData.geometryId = geometryId; // For armor attachables, the attachable references IDs like "geometry.humanoid.armor.chestplate" // which live in mobs.json and use cross-file v1.8.0 inheritance (→geometry.zombie). // Instead, use the equivalent self-contained geometry from player_armor.json: // "geometry.humanoid.armor.X" → "geometry.player.armor.X" let armorGeoId; if (geometryId.startsWith("geometry.humanoid.armor.")) { armorGeoId = geometryId.replace("geometry.humanoid.armor.", "geometry.player.armor."); } let geoResult = null; if (armorGeoId) { // Try the player_armor.json version first (self-contained inheritance chain) geoResult = await this._loadVanillaGeometry(armorGeoId); } if (!geoResult) { geoResult = await this._loadVanillaGeometry(geometryId); } if (geoResult) { modelData.geometry = (0, VanillaGeometryTransforms_1.applyGeometryTransforms)(geoResult.geometry, geometryId); modelData.modelDefinition = geoResult.definition; } else { Log_1.default.debugAlert(`Failed to load geometry for attachable ${shortId}: ${geometryId}`); } } // Get default texture const textures = attachableResource.textures; if (textures && textures["default"]) { let texturePath = textures["default"]; if (texturePath) { // Remove file extension if present if (texturePath.endsWith(".png") || texturePath.endsWith(".tga")) { texturePath = texturePath.substring(0, texturePath.lastIndexOf(".")); } modelData.texturePath = texturePath; modelData.textureUrl = CreatorToolsHost_1.default.contentWebRoot + `res/latest/van/serve/resource_pack/${texturePath}.png`; const textureData = await this.loadVanillaTexture(texturePath); if (textureData) { modelData.textureData = textureData; } } } // For armor-type attachables, load the humanoid base model (Steve) so // the armor can be rendered on top of a body silhouette. // Armor geometries reference identifiers containing "humanoid" or "armor". const isArmorType = modelData.geometryId && (modelData.geometryId.includes("humanoid") || modelData.geometryId.includes("armor")); if (isArmorType) { const baseGeoResult = await this._loadVanillaGeometry("geometry.humanoid.custom"); if (baseGeoResult) { modelData.baseGeometry = baseGeoResult.geometry; modelData.baseModelDefinition = baseGeoResult.definition; modelData.baseTextureUrl = CreatorToolsHost_1.default.contentWebRoot + "res/latest/van/serve/resource_pack/textures/entity/steve.png"; const steveTexture = await this.loadVanillaTexture("textures/entity/steve"); if (steveTexture) { modelData.baseTextureData = steveTexture; } } } this._attachableModelDataCache.set(shortId, modelData); return modelData; } static async _loadVanillaGeometry(geometryId) { // Parse geometry ID to find the file // Format is typically "geometry.mob_name" or "geometry.mob_name.variant" const parts = geometryId.split("."); if (parts.length < 2) return null; const baseName = parts[1]; // e.g., "pig" from "geometry.pig" or "tropicalfish_a" from "geometry.tropicalfish_a" // Generate possible file names to try // Some entities use underscores in file names but not in geometry IDs, or vice versa const baseNameWithUnderscore = this._addUnderscoreBeforeLastPart(baseName); const baseNameWithoutSuffix = baseName.replace(/_[a-z]$/, ""); // Remove _a, _b suffixes const baseNameWithUnderscoreNoSuffix = this._addUnderscoreBeforeLastPart(baseNameWithoutSuffix); const possibleBaseNames = [baseName, baseNameWithUnderscore, baseNameWithoutSuffix, baseNameWithUnderscoreNoSuffix]; // Remove duplicates const uniqueBaseNames = [...new Set(possibleBaseNames)]; const possibleFiles = []; for (const name of uniqueBaseNames) { possibleFiles.push(`${name}.geo.json`); possibleFiles.push(`${name}.v3.geo.json`); possibleFiles.push(`${name}.v2.geo.json`); } // First pass: look for exact geometry ID match for (const fileName of possibleFiles) { const file = await Database_1.default.getPreviewVanillaFile(`/resource_pack/models/entity/${fileName}`); if (file) { const geoDef = await ModelGeometryDefinition_1.default.ensureOnFile(file); if (geoDef) { const exactGeometry = geoDef.getById(geometryId); if (exactGeometry) { return { geometry: exactGeometry, definition: geoDef }; } } } } // Second pass: if no exact match found, fall back to default geometry from first file with one for (const fileName of possibleFiles) { const file = await Database_1.default.getPreviewVanillaFile(`/resource_pack/models/entity/${fileName}`); if (file) { const geoDef = await ModelGeometryDefinition_1.default.ensureOnFile(file); if (geoDef && geoDef.defaultGeometry) { Log_1.default.debugAlert(`VanillaProjectManager: No exact match for geometry "${geometryId}", falling back to default from ${fileName}`); return { geometry: geoDef.defaultGeometry, definition: geoDef }; } } } // Third pass: scan all files in /models/entity/ for an exact geometry ID match. // This handles attachable geometries (e.g., "geometry.humanoid.armor.chestplate" // which lives in "player_armor.json") where the baseName heuristic fails. const vanillaFolder = await Database_1.default.getPreviewVanillaFolder(); if (vanillaFolder) { const modelsFolder = await vanillaFolder.getFolderFromRelativePath("/resource_pack/models/entity/"); if (modelsFolder) { await modelsFolder.load(); for (const fileName in modelsFolder.files) { if (!fileName.endsWith(".json")) continue; // Skip files we already tried in the first two passes if (possibleFiles.includes(fileName)) continue; const file = modelsFolder.files[fileName]; if (file) { const geoDef = await ModelGeometryDefinition_1.default.ensureOnFile(file); if (geoDef) { const exactGeometry = geoDef.getById(geometryId); if (exactGeometry) { // Try v1.8.0 inheritance resolution first (e.g., player_armor.json) const resolved = this._resolveV18Inheritance(geometryId, geoDef); if (resolved) { return { geometry: resolved, definition: geoDef }; } return { geometry: exactGeometry, definition: geoDef }; } } } } } } // Fourth pass: scan /models/ root directory (not just /models/entity/) for v1.8.0 format files. // The mobs.json file in /models/ contains humanoid armor geometry with inheritance syntax. if (vanillaFolder) { const modelsRootFolder = await vanillaFolder.getFolderFromRelativePath("/resource_pack/models/"); if (modelsRootFolder) { await modelsRootFolder.load(); for (const fileName in modelsRootFolder.files) { if (!fileName.endsWith(".json")) continue; const file = modelsRootFolder.files[fileName]; if (file) { const geoDef = await ModelGeometryDefinition_1.default.ensureOnFile(file); if (geoDef) { const exactGeometry = geoDef.getById(geometryId); if (exactGeometry) { // Check if this is a v1.8.0 format with inheritance that needs resolution const resolved = this._resolveV18Inheritance(geometryId, geoDef); if (resolved) { return { geometry: resolved, definition: geoDef }; } return { geometry: exactGeometry, definition: geoDef }; } } } } } } return null; } /** * Resolve v1.8.0 geometry inheritance chain. * * V1.8.0 format uses keys like `"geometry.X:geometry.Y"` where Y is the parent. * Each child provides bone overrides: `inflate` modifies cube inflation, * `reset: true` removes all cubes from a bone (hides it), `neverRender` controls visibility. * * Example chain for chestplate: * geometry.humanoid.armor.chestplate:geometry.humanoid.armor1 * → geometry.humanoid.armor1:geometry.zombie * → geometry.zombie (base with all cubes) * * Returns a flattened IGeometry with all inheritance resolved. */ static _resolveV18Inheritance(geometryId, geoDef) { const data = geoDef.data; if (!data) return null; // Find the key that matches this geometry ID (may include ":parent" suffix) let matchingKey; let parentId; for (const key of Object.keys(data)) { if (key === geometryId || key.startsWith(geometryId + ":")) { matchingKey = key; const colonIndex = key.indexOf(":"); if (colonIndex > 0) { parentId = key.substring(colonIndex + 1); } break; } } if (!matchingKey) return null; if (!parentId) { // No inheritance — return as-is return null; } // Resolve parentId recursively const parentGeometry = this._resolveV18InheritanceFrom(parentId, data); if (!parentGeometry || !parentGeometry.bones) return null; // Get the child overrides const childData = data[matchingKey]; if (!childData || !childData.bones) return null; // Deep clone parent bones to avoid mutating the original const resolvedBones = JSON.parse(JSON.stringify(parentGeometry.bones)); // Apply child bone overrides for (const childBone of childData.bones) { const parentBoneIndex = resolvedBones.findIndex((b) => b.name === childBone.name); if (parentBoneIndex >= 0) { const parentBone = resolvedBones[parentBoneIndex]; if (childBone.reset) { // reset: true removes all cubes from this bone (makes it invisible) parentBone.cubes = undefined; parentBone.poly_mesh = undefined; parentBone.texture_meshes = undefined; } else if (childBone.cubes && childBone.cubes.length > 0) { // Child provides complete cube definitions — replace parent cubes. // This is used by sheep wool overlay: child bones have different UVs // and inflate values that map to the wool section of the texture. parentBone.cubes = JSON.parse(JSON.stringify(childBone.cubes)); // Preserve parent's bind_pose_rotation if child overrides it if (childBone.bind_pose_rotation) { parentBone.bind_pose_rotation = [...childBone.bind_pose_rotation]; } // Preserve parent's pivot if child overrides it if (childBone.pivot) { parentBone.pivot = [...childBone.pivot]; } } else { // No cubes in child — apply property overrides to existing parent cubes if (childBone.inflate !== undefined && parentBone.cubes) { for (const cube of parentBone.cubes) { cube.inflate = childBone.inflate; } } if (childBone.neverRender !== undefined) { parentBone.neverRender = childBone.neverRender; } } } } // Filter out neverRender bones and bones with no visual content const visibleBones = resolvedBones.filter((b) => { if (b.neverRender === true) return false; return true; }); return { description: parentGeometry.description || { identifier: geometryId, texture_width: childData.texturewidth || parentGeometry.texturewidth || 64, texture_height: childData.textureheight || parentGeometry.textureheight || 32, visible_bounds_width: 2, visible_bounds_height: 2, visible_bounds_offset: [0, 1, 0], }, bones: visibleBones, texturewidth: childData.texturewidth || parentGeometry.texturewidth, textureheight: childData.textureheight || parentGeometry.textureheight, }; } /** * Recursively resolve a v1.8.0 geometry ID within a single file's data. */ static _resolveV18InheritanceFrom(geoId, data) { // Find the key for this geometry ID let matchingKey; let parentId; for (const key of Object.keys(data)) { if (key === "format_version") continue; if (key === geoId || key.startsWith(geoId + ":")) { matchingKey = key; const colonIndex = key.indexOf(":"); if (colonIndex > 0) { parentId = key.substring(colonIndex + 1); } break; } } if (!matchingKey) return null; const geoData = data[matchingKey]; if (!parentId) { // Base geometry — no parent, return directly return geoData; } // Recursively resolve parent const parentGeo = this._resolveV18InheritanceFrom(parentId, data); if (!parentGeo || !parentGeo.bones) return geoData; // Deep clone parent bones const resolvedBones = JSON.parse(JSON.stringify(parentGeo.bones)); // Apply child overrides if (geoData.bones) { for (const childBone of geoData.bones) { const parentBoneIndex = resolvedBones.findIndex((b) => b.name === childBone.name); if (parentBoneIndex >= 0) { const parentBone = resolvedBones[parentBoneIndex]; if (childBone.reset) { parentBone.cubes = undefined; parentBone.poly_mesh = undefined; parentBone.texture_meshes = undefined; } else { if (childBone.inflate !== undefined && parentBone.cubes) { for (const cube of parentBone.cubes) { cube.inflate = childBone.inflate; } } if (childBone.neverRender !== undefined) { parentBone.neverRender = childBone.neverRender; } } } } } return { ...parentGeo, ...geoData, bones: resolvedBones, }; } /** * Adds an underscore before the last "word" in a name. * e.g., "tropicalfish" -> "tropical_fish", "tropicalfish_a" -> "tropical_fish_a" */ static _addUnderscoreBeforeLastPart(name) { // Common suffixes that indicate a word boundary const commonWords = [ "fish", "spawner", "golem", "spider", "skeleton", "zombie", "creeper", "slime", "cube", "guardian", "shulker", "villager", "illager", "pillager", "witch", "horse", "donkey", "mule", "llama", "wolf", "cat", "ocelot", "fox", "panda", "bee", "hoglin", "piglin", "strider", "axolotl", "goat", "frog", "warden", "sniffer", "camel", "breeze", "bogged", "armadillo", ]; for (const word of commonWords) { const index = name.indexOf(word); if (index > 0 && name[index - 1] !== "_") { return name.substring(0, index) + "_" + name.substring(index); } } return name; } /** * Load a vanilla texture by its resource path (e.g., "textures/entity/pig/pig"). * Returns the raw PNG bytes from the serve vanilla folder, or null if not found. */ static async loadVanillaTexture(texturePath) { // texturePath is like "textures/entity/pig/pig" // Use serve folder which has PNG versions of all textures (including those that are TGA in preview/release) const vanillaFolder = await Database_1.default.getServeVanillaFolder(); if (!vanillaFolder) { Log_1.default.debugAlert("Could not get serve vanilla folder"); return null; } const fullPath = `/resource_pack/${texturePath}.png`; const file = await vanillaFolder.getFileFromRelativePath(fullPath); if (!file) { Log_1.default.debugAlert(`Vanilla texture file not found: ${fullPath}`); // Try to list the directory to see what's there const dirPath = `/resource_pack/${texturePath.substring(0, texturePath.lastIndexOf("/"))}`; return null; } await file.loadContent(); if (file.content instanceof Uint8Array) { return file.content; } Log_1.default.debugAlert(`Vanilla texture file content is not Uint8Array: ${typeof file.content}`); return null; } static getBlocksCatalogDirect() { return this.blocksCatalog; } static async getBlocksCatalog() { if (!VanillaProjectManager.blocksCatalog) { const file = await Database_1.default.getPreviewVanillaFile("/resource_pack/blocks.json"); if (file) { const blockCat = new BlocksCatalogDefinition_1.default(); blockCat.file = file; await blockCat.load(); this.blocksCatalog = blockCat; } } return this.blocksCatalog; } static getTerrainTexturesCatalogDirect() { return this.terrainTextureCatalog; } static async getTerrainTexturesCatalog() { if (!VanillaProjectManager.terrainTextureCatalog) { const file = await Database_1.default.getPreviewVanillaFile("/resource_pack/textures/terrain_texture.json"); if (file) { const terrainCat = new TerrainTextureCatalogDefinition_1.default(); terrainCat.file = file; await terrainCat.load(); VanillaProjectManager.terrainTextureCatalog = terrainCat; } } return VanillaProjectManager.terrainTextureCatalog; } static async getItemTexturesCatalog() { if (!VanillaProjectManager.itemTextureCatalog) { const file = await Database_1.default.getPreviewVanillaFile("/resource_pack/textures/item_texture.json"); if (file) { const itemCat = new ItemTextureCatalogDefinition_1.default(); itemCat.file = file; await itemCat.load(); VanillaProjectManager.itemTextureCatalog = itemCat; } } return VanillaProjectManager.itemTextureCatalog; } static async getSoundDefinitionCatalog() { if (!VanillaProjectManager.soundDefinitionCatalog) { const file = await Database_1.default.getPreviewVanillaFile("/resource_pack/sounds/sound_definitions.json"); if (file) { const soundDefinitionCat = new SoundDefinitionCatalogDefinition_1.default(); soundDefinitionCat.file = file; await soundDefinitionCat.load(); VanillaProjectManager.soundDefinitionCatalog = soundDefinitionCat; } } return VanillaProjectManager.soundDefinitionCatalog; } } exports.default = VanillaProjectManager;