From 21c99145e19a770fb107b6a9a83016048e6d2f33 Mon Sep 17 00:00:00 2001 From: Markus Bordihn Date: Mon, 15 Jun 2026 02:22:35 +0200 Subject: [PATCH 1/8] Add Easy Model Entities exporter and initial configuration files. --- plugins.json | 14 + plugins/easy_model_entities/LICENSE.MD | 21 + plugins/easy_model_entities/about.md | 47 + plugins/easy_model_entities/changelog.json | 24 + .../easy_model_entities.js | 3102 +++++++++ plugins/easy_model_entities/icon.png | Bin 0 -> 5255 bytes plugins/easy_model_entities/members.yml | 2 + .../src/BlockbenchAdapter.js | 221 + plugins/easy_model_entities/src/LICENSE.MD | 21 + .../src/builders/exporter.js | 125 + .../src/builders/packMeta.js | 46 + .../src/builders/profileDiff.js | 53 + .../src/builders/readme.js | 37 + .../src/builders/renderProfile.js | 111 + .../src/builders/serverProfile.js | 146 + .../src/i18n/translations.js | 219 + plugins/easy_model_entities/src/index.js | 307 + .../src/model/ModelDimensions.js | 105 + .../src/model/PresetDetector.js | 110 + .../src/model/Validator.js | 250 + .../src/model/presetTypes.js | 346 + .../src/model/templates.js | 74 + .../src/model/versionMatrix.js | 85 + .../easy_model_entities/src/package-lock.json | 6031 +++++++++++++++++ plugins/easy_model_entities/src/package.json | 28 + .../src/resources/action_icon.svg | 34 + .../src/resources/icon.png | Bin 0 -> 5255 bytes .../src/resources/readme.md | 47 + .../src/tests/exportDialog.test.js | 184 + .../src/tests/exporter.test.js | 136 + .../src/tests/fixtureData.js | 45 + .../src/tests/mdTransform.js | 5 + .../src/tests/modelDimensions.test.js | 81 + .../src/tests/presetDetector.test.js | 70 + .../src/tests/readme.test.js | 29 + .../src/tests/resourceLocation.test.js | 57 + .../src/tests/schemaConsistency.test.js | 117 + .../src/tests/templates.test.js | 93 + .../src/tests/validation.test.js | 139 + .../src/tests/versionMatrix.test.js | 29 + .../src/ui/exportDialog.js | 544 ++ .../src/utils/ResourceLocation.js | 70 + .../easy_model_entities/src/webpack.config.js | 48 + 43 files changed, 13253 insertions(+) create mode 100644 plugins/easy_model_entities/LICENSE.MD create mode 100644 plugins/easy_model_entities/about.md create mode 100644 plugins/easy_model_entities/changelog.json create mode 100644 plugins/easy_model_entities/easy_model_entities.js create mode 100644 plugins/easy_model_entities/icon.png create mode 100644 plugins/easy_model_entities/members.yml create mode 100644 plugins/easy_model_entities/src/BlockbenchAdapter.js create mode 100644 plugins/easy_model_entities/src/LICENSE.MD create mode 100644 plugins/easy_model_entities/src/builders/exporter.js create mode 100644 plugins/easy_model_entities/src/builders/packMeta.js create mode 100644 plugins/easy_model_entities/src/builders/profileDiff.js create mode 100644 plugins/easy_model_entities/src/builders/readme.js create mode 100644 plugins/easy_model_entities/src/builders/renderProfile.js create mode 100644 plugins/easy_model_entities/src/builders/serverProfile.js create mode 100644 plugins/easy_model_entities/src/i18n/translations.js create mode 100644 plugins/easy_model_entities/src/index.js create mode 100644 plugins/easy_model_entities/src/model/ModelDimensions.js create mode 100644 plugins/easy_model_entities/src/model/PresetDetector.js create mode 100644 plugins/easy_model_entities/src/model/Validator.js create mode 100644 plugins/easy_model_entities/src/model/presetTypes.js create mode 100644 plugins/easy_model_entities/src/model/templates.js create mode 100644 plugins/easy_model_entities/src/model/versionMatrix.js create mode 100644 plugins/easy_model_entities/src/package-lock.json create mode 100644 plugins/easy_model_entities/src/package.json create mode 100644 plugins/easy_model_entities/src/resources/action_icon.svg create mode 100644 plugins/easy_model_entities/src/resources/icon.png create mode 100644 plugins/easy_model_entities/src/resources/readme.md create mode 100644 plugins/easy_model_entities/src/tests/exportDialog.test.js create mode 100644 plugins/easy_model_entities/src/tests/exporter.test.js create mode 100644 plugins/easy_model_entities/src/tests/fixtureData.js create mode 100644 plugins/easy_model_entities/src/tests/mdTransform.js create mode 100644 plugins/easy_model_entities/src/tests/modelDimensions.test.js create mode 100644 plugins/easy_model_entities/src/tests/presetDetector.test.js create mode 100644 plugins/easy_model_entities/src/tests/readme.test.js create mode 100644 plugins/easy_model_entities/src/tests/resourceLocation.test.js create mode 100644 plugins/easy_model_entities/src/tests/schemaConsistency.test.js create mode 100644 plugins/easy_model_entities/src/tests/templates.test.js create mode 100644 plugins/easy_model_entities/src/tests/validation.test.js create mode 100644 plugins/easy_model_entities/src/tests/versionMatrix.test.js create mode 100644 plugins/easy_model_entities/src/ui/exportDialog.js create mode 100644 plugins/easy_model_entities/src/utils/ResourceLocation.js create mode 100644 plugins/easy_model_entities/src/webpack.config.js diff --git a/plugins.json b/plugins.json index c9593818..722c49e3 100644 --- a/plugins.json +++ b/plugins.json @@ -1534,5 +1534,19 @@ "website": "https://svdex.moe", "min_version": "4.8.0", "has_changelog": true + }, + "easy_model_entities": { + "title": "Easy Model Entities Exporter", + "author": "Markus Bordihn", + "icon": "icon.png", + "description": "Export Blockbench models for the Easy Model Entities mod (Minecraft: Java Edition) as ready-to-install Data Pack and Resource Pack files.", + "tags": ["Minecraft: Java Edition", "Exporter", "Entity"], + "version": "0.1.0", + "min_version": "4.9.0", + "variant": "desktop", + "has_changelog": true, + "website": "https://github.com/MarkusBordihn/BOs-Easy-Model-Entities", + "repository": "https://github.com/MarkusBordihn/BOs-Easy-Model-Entities", + "bug_tracker": "https://github.com/MarkusBordihn/BOs-Easy-Model-Entities/issues" } } diff --git a/plugins/easy_model_entities/LICENSE.MD b/plugins/easy_model_entities/LICENSE.MD new file mode 100644 index 00000000..735887cf --- /dev/null +++ b/plugins/easy_model_entities/LICENSE.MD @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Markus Bordihn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/easy_model_entities/about.md b/plugins/easy_model_entities/about.md new file mode 100644 index 00000000..8cfdcffb --- /dev/null +++ b/plugins/easy_model_entities/about.md @@ -0,0 +1,47 @@ +# Easy Model Entities Exporter + +> **Beta:** This is an early release to gather feedback. Some settings, file formats and the export +> output may still change before the final version. Please report problems and ideas on the +> [issue tracker](https://github.com/MarkusBordihn/BOs-Easy-Model-Entities/issues). + +Export the current Blockbench project for the +[Easy Model Entities](https://github.com/MarkusBordihn/BOs-Easy-Model-Entities) mod on +**Minecraft: Java Edition**. + +This plugin only generates the pack files. The **Easy Model Entities mod (Forge or Fabric) must be +installed** in Minecraft for them to work. Without the mod the generated files do nothing. + +## How to use + +1. Open or create your model in Blockbench as a **Modded Entity** (File > New > Java/Modded Entity). + Block and item formats are not supported. +2. Run **File > Export > Export Easy Model Entities**. +3. Choose the **Export Type**: + - **Standalone: Data Pack + Resource Pack (ZIP)** — a single archive with a `datapack.zip` and a + `resourcepack.zip` plus a `README.md` describing how to install both into Minecraft. + - **Standalone: write into mod project** — writes all files directly into a selected + `src/main/resources` directory. + - **Model only: mod integration (no data pack)** — writes only the render profile, model and + texture into a mod project. Use this when the mod ships its own entity classes and only needs + the visual side. +4. Choose the **Type** (only for Standalone exports): + - **Entity** — a living entity controlled by the mod's built-in AI presets. + - **Block Entity** — a static block that can play animations. +5. Pick a **Preset** that best matches your model. The plugin auto-detects a fitting preset from + your bone names. Most models need nothing else; tick **Customize settings** (requires the + *Show advanced customization* Blockbench setting) only to fine-tune dimensions, movement, + attributes, rendering or animation. +6. Set the **Namespace (mod id)** and **Profile ID**, and choose the Minecraft target version. + +Exported settings are saved inside the `.bbmodel` project and restored on the next export. + +> Note: `.bbmodel` is used as the runtime model format. It may be replaced by a dedicated format +> in a future version of the mod. + +## Blockbench settings + +Two optional settings are available under **File > Preferences > Settings > Export**: + +- **Show advanced customization** — unlocks the *Customize settings* checkbox in the export dialog. +- **Show experimental presets** — reveals presets that are loaded by the mod but whose specialized + movement or behavior is still in development. Off by default. diff --git a/plugins/easy_model_entities/changelog.json b/plugins/easy_model_entities/changelog.json new file mode 100644 index 00000000..354ae6ad --- /dev/null +++ b/plugins/easy_model_entities/changelog.json @@ -0,0 +1,24 @@ +{ + "0.1.0": { + "title": "Beta Release for testing and feedback", + "author": "Markus Bordihn", + "date": "2026-06-13", + "categories": [ + { + "title": "Features", + "list": [ + "Export the current project as Easy Model Entities Data Pack and Resource Pack files.", + "Preset auto-detection from bone names (humanoid, quadruped, aquatic, winged, arthropod, cuboid, floating, static).", + "Three export types: Standalone ZIP archive, write into mod project, or model-only for mod integrations.", + "Entity and Block Entity support with separate server profile subfolders.", + "Minimal (diff-based) profile output: only fields that differ from the mod defaults are written.", + "Minecraft target version selector (1.20.1 available).", + "Optional advanced customization of dimensions, movement, behavior, attributes, rendering and animation.", + "Experimental presets hidden behind a Blockbench setting (off by default).", + "Validation with blocking errors and performance budget warnings.", + "EN and DE translations." + ] + } + ] + } +} diff --git a/plugins/easy_model_entities/easy_model_entities.js b/plugins/easy_model_entities/easy_model_entities.js new file mode 100644 index 00000000..a45520a8 --- /dev/null +++ b/plugins/easy_model_entities/easy_model_entities.js @@ -0,0 +1,3102 @@ +/******/ +(() => { // webpackBootstrap + /******/ + var __webpack_modules__ = ({ + + /***/ 833 + (module, __unused_webpack_exports, __webpack_require__) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + class BlockbenchAdapter { + static PROJECT_PROPERTY = 'eme_export'; + + static #base64ToBytes(base64) { + const clean = String(base64).replace(/^data:[^,]*,/, ''); + const binary = + typeof atob === 'function' ? atob(clean) : Buffer.from(clean, + 'base64').toString('binary'); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.codePointAt(i) & 0xff; + } + return bytes; + } + + // The texture currently selected/active in Blockbench; only this texture is + // exported when the project contains multiple textures. + static getActiveTexture() { + if (typeof Texture === 'undefined') { + return null; + } + return Texture.selected || Texture.getDefault() || (Texture.all?.[0]) + || null; + } + + static getTextureCount() { + if (typeof Texture === 'undefined' || !Texture.all) { + return 0; + } + return Texture.all.length; + } + + static getModelStats() { + const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; + const groups = typeof Group !== 'undefined' && Group.all ? Group.all + : []; + const texture = BlockbenchAdapter.getActiveTexture(); + + let maxDepth = 0; + groups.forEach((group) => { + let depth = 1; + let parent = group.parent; + while (parent && parent !== 'root' && typeof parent === 'object') { + depth += 1; + parent = parent.parent; + } + if (depth > maxDepth) { + maxDepth = depth; + } + }); + + return { + hasModel: cubes.length > 0 || groups.length > 0, + hasTexture: !!texture, + cubeCount: cubes.length, + boneCount: groups.length, + hierarchyDepth: maxDepth, + boneNames: groups.map((group) => group.name), + textureWidth: texture ? texture.width : undefined, + textureHeight: texture ? texture.height : undefined + }; + } + + static getModelBytes() { + return Codecs.project.compile({raw: false}); + } + + static getModelBounds() { + const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; + if (cubes.length === 0) { + return null; + } + const bounds = { + minX: Infinity, + minY: Infinity, + minZ: Infinity, + maxX: -Infinity, + maxY: -Infinity, + maxZ: -Infinity + }; + cubes.forEach((cube) => { + const from = cube.from || [0, 0, 0]; + const to = cube.to || [0, 0, 0]; + bounds.minX = Math.min(bounds.minX, from[0], to[0]); + bounds.minY = Math.min(bounds.minY, from[1], to[1]); + bounds.minZ = Math.min(bounds.minZ, from[2], to[2]); + bounds.maxX = Math.max(bounds.maxX, from[0], to[0]); + bounds.maxY = Math.max(bounds.maxY, from[1], to[1]); + bounds.maxZ = Math.max(bounds.maxZ, from[2], to[2]); + }); + return bounds; + } + + static getProjectName() { + if (typeof Project === 'undefined' || !Project) { + return ''; + } + if (Project.geometry_name) { + return Project.geometry_name; + } + if (Project.name) { + return Project.name; + } + if (typeof Project.getDisplayName === 'function') { + return Project.getDisplayName(false); + } + return ''; + } + + static getTextureBytes() { + const texture = BlockbenchAdapter.getActiveTexture(); + if (!texture) { + return new Uint8Array(0); + } + return BlockbenchAdapter.#base64ToBytes(texture.getBase64()); + } + + static loadSettings() { + if (typeof Project === 'undefined' || !Project) { + return null; + } + const stored = Project[BlockbenchAdapter.PROJECT_PROPERTY]; + if (stored && typeof stored === 'object') { + return structuredClone(stored); + } + return null; + } + + static saveSettings(settings) { + if (typeof Project !== 'undefined' && Project) { + Project[BlockbenchAdapter.PROJECT_PROPERTY] = structuredClone( + settings); + if (typeof Blockbench !== 'undefined' && Blockbench.dispatchEvent) { + Blockbench.dispatchEvent('eme_settings_saved', {settings}); + } + } + } + + static #zipToUint8(files) { + const zip = new JSZip(); + files.forEach((file) => { + zip.file(file.path, file.content); + }); + return zip.generateAsync({type: 'uint8array'}); + } + + // Wraps a ready-to-drop datapack.zip and resourcepack.zip plus the README + // into a single outer ZIP, so the user can move the inner ZIPs straight into + // their datapacks/ and resourcepacks/ folders without unpacking. + static exportPackBundle(bundle, name) { + return Promise.all([ + BlockbenchAdapter.#zipToUint8(bundle.datapack), + BlockbenchAdapter.#zipToUint8(bundle.resourcepack) + ]).then(([datapackZip, resourcepackZip]) => { + const outer = new JSZip(); + outer.file(bundle.readme.path, bundle.readme.content); + outer.file('datapack.zip', datapackZip); + outer.file('resourcepack.zip', resourcepackZip); + return outer.generateAsync({type: 'blob'}); + }).then((content) => { + return new Promise((resolve) => { + Blockbench.export( + { + type: 'Zip Archive', + extensions: ['zip'], + name: name, + content: content, + savetype: 'zip' + }, + (path) => resolve(path) + ); + }); + }); + } + + static listExistingFiles(rootDir, files) { + const fs = __webpack_require__(896); + const path = __webpack_require__(928); + return files + .map((file) => path.join(rootDir, file.path)) + .filter((fullPath) => fs.existsSync(fullPath)); + } + + static writeToDirectory(rootDir, files) { + const fs = __webpack_require__(896); + const path = __webpack_require__(928); + files.forEach((file) => { + const fullPath = path.join(rootDir, file.path); + fs.mkdirSync(path.dirname(fullPath), {recursive: true}); + const data = + file.binary && file.content instanceof Uint8Array ? Buffer.from( + file.content) : file.content; + fs.writeFileSync(fullPath, data); + }); + } + + static pickDirectory(title) { + return Blockbench.pickDirectory( + {title: title, resource_id: 'eme_mod_project'}); + } + } + + module.exports = {BlockbenchAdapter}; + + /***/ + }, + + /***/ 869 + (module, __unused_webpack_exports, __webpack_require__) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + const {buildServerProfile} = __webpack_require__(790); + const {buildRenderProfile} = __webpack_require__(255); + const { + buildDataPackMcmeta, + buildResourcePackMcmeta + } = __webpack_require__(600); + const {buildReadme} = __webpack_require__(250); + + function toJson(value) { + return JSON.stringify(value, null, 2) + '\n'; + } + + function buildProfiles(settings) { + return { + serverProfile: buildServerProfile(settings), + renderProfile: buildRenderProfile(settings) + }; + } + + function dataPaths(settings) { + const ns = settings.namespace; + const id = settings.profileId; + // Server profiles live in an entity/ or block_entity/ subfolder. Models and + // textures stay at the mod's auto-resolved default location so the render + // profile never has to spell out a model/texture path. + const modelType = settings.modelType || 'entity'; + return { + profile: `data/${ns}/easy_model_entities/profiles/${modelType}/${id}.json`, + renderProfile: `assets/${ns}/easy_model_entities/render_profiles/${id}.json`, + model: `assets/${ns}/easy_model_entities/models/${id}.bbmodel`, + texture: `assets/${ns}/textures/entity/${id}.png` + }; + } + + function datapackFiles(settings, serverProfile) { + return [ + { + path: 'pack.mcmeta', content: toJson(buildDataPackMcmeta(settings)), + binary: false + }, + { + path: dataPaths(settings).profile, content: toJson(serverProfile), + binary: false + } + ]; + } + + function resourcepackFiles(settings, renderProfile, options) { + const paths = dataPaths(settings); + return [ + { + path: 'pack.mcmeta', + content: toJson(buildResourcePackMcmeta(settings)), + binary: false + }, + { + path: paths.renderProfile, + content: toJson(renderProfile), + binary: false + }, + {path: paths.model, content: options.modelBytes, binary: true}, + {path: paths.texture, content: options.textureBytes, binary: true} + ]; + } + +// Bundle to wrap into the outer export ZIP: a ready-to-drop datapack.zip and +// resourcepack.zip plus an install README. + function buildPackBundle(settings, options) { + const opts = options || {}; + const {serverProfile, renderProfile} = buildProfiles(settings); + return { + readme: { + path: 'README.md', + content: buildReadme(settings), + binary: false + }, + datapack: datapackFiles(settings, serverProfile), + resourcepack: resourcepackFiles(settings, renderProfile, opts), + serverProfile, + renderProfile + }; + } + + function buildModProjectFiles(settings, options) { + const opts = options || {}; + const renderProfile = buildRenderProfile(settings); + const paths = dataPaths(settings); + + const files = []; + let serverProfile = null; + // Model-only export (mod integration, e.g. the Mimic example): the mod ships + // its own entity classes, so no server profile / data pack is written. + if (!settings.modelOnly) { + serverProfile = buildServerProfile(settings); + files.push({ + path: paths.profile, content: toJson(serverProfile), + binary: false + }); + } + + files.push( + { + path: paths.renderProfile, content: toJson(renderProfile), + binary: false + }, + {path: paths.model, content: opts.modelBytes, binary: true}, + {path: paths.texture, content: opts.textureBytes, binary: true}); + + return {files, serverProfile, renderProfile}; + } + + module.exports = { + buildPackBundle, + buildModProjectFiles, + dataPaths, + datapackFiles, + resourcepackFiles + }; + + /***/ + }, + + /***/ 600 + (module, __unused_webpack_exports, __webpack_require__) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + const {getPackFormats} = __webpack_require__(954); + + function buildMcmeta(settings, packFormat, description) { + const formats = getPackFormats(settings.targetVersion); + if (!formats) { + throw new Error( + `Unsupported or disabled target version: ${settings.targetVersion}`); + } + return { + pack: { + pack_format: formats[packFormat], + description: description + } + }; + } + + function buildDataPackMcmeta(settings) { + return buildMcmeta(settings, 'dataFormat', + 'Easy Model Entities server profiles'); + } + + function buildResourcePackMcmeta(settings) { + return buildMcmeta(settings, 'resourceFormat', + 'Easy Model Entities render assets'); + } + + module.exports = {buildDataPackMcmeta, buildResourcePackMcmeta}; + + /***/ + }, + + /***/ 640 + (module) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Helpers to emit minimal (diff-based) profiles: only values that deviate from +// the defaults the mod derives from preset_type are written. + + function valuesDiffer(a, b) { + if (Array.isArray(a) || Array.isArray(b)) { + return JSON.stringify(a) !== JSON.stringify(b); + } + return a !== b; + } + +// Returns a new object containing only the keys whose value differs from the +// matching key in defaults. + function diffFlat(values, defaults) { + const result = {}; + Object.keys(values).forEach((key) => { + if (valuesDiffer(values[key], defaults[key])) { + result[key] = values[key]; + } + }); + return result; + } + + function isEmpty(object) { + return !object || Object.keys(object).length === 0; + } + +// Assigns section to target under key only when it has at least one entry. + function assignIfPresent(target, key, section) { + if (!isEmpty(section)) { + target[key] = section; + } + } + + module.exports = {valuesDiffer, diffFlat, isEmpty, assignIfPresent}; + + /***/ + }, + + /***/ 250 + (module, __unused_webpack_exports, __webpack_require__) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + const {ResourceLocation} = __webpack_require__(20); + const TEMPLATE = __webpack_require__(377); + + function buildReadme(settings) { + const id = ResourceLocation.buildResourceLocation(settings.namespace, + settings.profileId); + const modelType = settings.modelType || 'entity'; + const serverProfileId = ResourceLocation.buildResourceLocation( + settings.namespace, `${modelType}/${settings.profileId}`); + return TEMPLATE + .replaceAll('{{id}}', id) + .replaceAll('{{serverProfileId}}', serverProfileId) + .replaceAll('{{mcVersion}}', + `Minecraft: Java Edition ${settings.targetVersion}`) + .replaceAll('{{folderName}}', `${settings.namespace}_eme`); + } + + module.exports = {buildReadme}; + + /***/ + }, + + /***/ 255 + (module, __unused_webpack_exports, __webpack_require__) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + const { + bodyType, + animationMode, + presetRenderBounds, + isCustom, + MODEL_TYPE_BLOCK_ENTITY + } = __webpack_require__(151); + const {diffFlat, assignIfPresent} = __webpack_require__(640); + +// Block entities render as a static cuboid block, so they always use the +// "static" render preset regardless of their server-side block entity preset. + function renderPresetType(settings) { + return settings.modelType === MODEL_TYPE_BLOCK_ENTITY + ? 'static' : settings.presetType; + } + + function buildModelReference(settings) { + return `${settings.namespace}:easy_model_entities/models/${settings.profileId}`; + } + + function buildTextureReference(settings) { + return `${settings.namespace}:textures/entity/${settings.profileId}.png`; + } + + function buildRendering(settings) { + const [boundsWidth, boundsHeight, boundsOffsetY, shadowRadius] = + presetRenderBounds(renderPresetType(settings)); + return diffFlat({ + scale: settings.rendering.scale, + shadow_radius: settings.rendering.shadowRadius, + visible_bounds_width: settings.rendering.visibleBoundsWidth, + visible_bounds_height: settings.rendering.visibleBoundsHeight, + visible_bounds_offset: settings.rendering.visibleBoundsOffset.slice() + }, { + scale: 1, + shadow_radius: shadowRadius, + visible_bounds_width: boundsWidth, + visible_bounds_height: boundsHeight, + visible_bounds_offset: [0, boundsOffsetY, 0] + }); + } + + function buildAnimation(settings) { + const defaultMode = animationMode(renderPresetType(settings)); + const animation = diffFlat({ + mode: settings.animation.mode, + swing_speed: settings.animation.swingSpeed, + walk_speed_multiplier: settings.animation.walkSpeedMultiplier + }, { + mode: defaultMode, + swing_speed: 1, + walk_speed_multiplier: 1 + }); + // Animation timing is meaningless when animation is disabled. + if (settings.animation.mode === 'none') { + delete animation.swing_speed; + delete animation.walk_speed_multiplier; + } + return animation; + } + + function buildRenderProfile(settings) { + const renderPreset = renderPresetType(settings); + const custom = settings.modelType !== MODEL_TYPE_BLOCK_ENTITY + && isCustom(settings.presetType); + const profile = { + schema_version: settings.schemaVersion, + preset_type: renderPreset + }; + if (settings.version) { + profile.version = settings.version; + } + + if (custom || settings.host.bodyType !== bodyType(renderPreset)) { + profile.body_type = settings.host.bodyType; + } + + // model and texture follow the mod's conventional default path + // (namespace:easy_model_entities/models/id and namespace:textures/entity/id.png), + // so they are omitted and resolved automatically by the mod. + + assignIfPresent(profile, 'rendering', buildRendering(settings)); + assignIfPresent(profile, 'animation', buildAnimation(settings)); + + return profile; + } + + module.exports = { + buildRenderProfile, + buildModelReference, + buildTextureReference + }; + + /***/ + }, + + /***/ 790 + (module, __unused_webpack_exports, __webpack_require__) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + const { + presetDefaults, + presetDimensions, + movementDefaults, + behaviorModeFor, + isCustom, + MODEL_TYPE_BLOCK_ENTITY, + MODEL_TYPE_ENTITY + } = __webpack_require__(151); + const {diffFlat, assignIfPresent} = __webpack_require__(640); + + const DEFAULT_MAX_HEALTH = 10; + const DEFAULT_FOLLOW_RANGE = 16; + + function effectiveDefaults(settings) { + const preset = settings.presetType; + const move = settings.host.movementType; + const movement = movementDefaults(preset, move); + const mode = behaviorModeFor(preset, move); + const dimensions = settings.modelType === MODEL_TYPE_BLOCK_ENTITY + ? presetDefaults(preset, MODEL_TYPE_BLOCK_ENTITY).dimensions + : presetDimensions(preset); + return { + movement: { + speed: movement.speed, + step_height: movement.stepHeight, + gravity: movement.gravity + }, + behavior: { + mode: mode, + look_at_players: mode === 'idle_only' || mode === 'ambient', + random_stroll: move === 'ground' && mode === 'ambient' + }, + attributes: { + max_health: DEFAULT_MAX_HEALTH, + movement_speed: settings.movement.speed, + follow_range: DEFAULT_FOLLOW_RANGE + }, + dimensions: dimensions + }; + } + +// Entity host settings. The mod derives type/movement_type/body_type from the +// preset, so only the "custom" preset needs to spell them out; non-custom +// presets emit nothing here. + function buildEntity(settings, custom) { + if (!custom) { + return {}; + } + const host = settings.host; + return { + type: host.entityType, + movement_type: host.movementType, + body_type: host.bodyType + }; + } + + function buildDimensions(settings, defaults, custom) { + const values = { + width: settings.dimensions.width, + height: settings.dimensions.height, + eye_height: settings.dimensions.eyeHeight + }; + if (custom) { + return values; + } + return diffFlat(values, { + width: defaults.dimensions.width, + height: defaults.dimensions.height, + eye_height: defaults.dimensions.eyeHeight + }); + } + + function buildServerProfile(settings) { + const modelType = settings.modelType || MODEL_TYPE_ENTITY; + const blockEntity = modelType === MODEL_TYPE_BLOCK_ENTITY; + const custom = !blockEntity && isCustom(settings.presetType); + const defaults = effectiveDefaults(settings); + + const profile = { + schema_version: settings.schemaVersion, + model_type: modelType, + preset_type: settings.presetType + }; + if (settings.version) { + profile.version = settings.version; + } + + // The server profile lives in a "/" subfolder, so its derived + // id differs from the flat render profile id. The render profile must be named + // explicitly or the mod would look for "/" instead. + profile.client = { + render_profile: `${settings.namespace}:${settings.profileId}` + }; + + if (!blockEntity) { + assignIfPresent(profile, 'entity', buildEntity(settings, custom)); + } + + assignIfPresent(profile, 'dimensions', + buildDimensions(settings, defaults, custom)); + + // Block entities ignore movement, behavior and attributes. + if (!blockEntity) { + assignIfPresent(profile, 'movement', diffFlat({ + speed: settings.movement.speed, + step_height: settings.movement.stepHeight, + gravity: settings.movement.gravity + }, defaults.movement)); + + assignIfPresent(profile, 'behavior', diffFlat({ + mode: settings.behavior.mode, + look_at_players: settings.behavior.lookAtPlayers, + random_stroll: settings.behavior.randomStroll + }, defaults.behavior)); + + assignIfPresent(profile, 'attributes', diffFlat({ + max_health: settings.attributes.maxHealth, + movement_speed: settings.attributes.movementSpeed, + follow_range: settings.attributes.followRange + }, defaults.attributes)); + } + + return profile; + } + + module.exports = {buildServerProfile}; + + /***/ + }, + + /***/ 16 + (module) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Descriptive, human-readable labels so users see e.g. "Quadruped (4 legs, +// ground, standing)" instead of the cryptic "quadruped_still". Registered with +// Blockbench's translation system; falls back to English outside Blockbench. + + const EN = { + 'eme.dialog.title': 'Easy Model Entities Export', + 'eme.field.preset': 'Preset', + 'eme.field.namespace': 'Namespace (mod id)', + 'eme.field.profileId': 'Profile ID', + 'eme.field.version': 'Version (optional)', + 'eme.field.targetVersion': 'Minecraft Version', + 'eme.field.exportType': 'Export Type', + 'eme.field.modelType': 'Type', + 'eme.field.customize': 'Customize settings', + 'eme.field.hostEntityType': 'Host Entity Type', + 'eme.field.movementType': 'Movement Type', + 'eme.field.bodyType': 'Body Type', + 'eme.field.width': 'Width', + 'eme.field.height': 'Height', + 'eme.field.eyeHeight': 'Eye Height', + 'eme.field.speed': 'Speed', + 'eme.field.stepHeight': 'Step Height', + 'eme.field.gravity': 'Gravity', + 'eme.field.behaviorMode': 'Behavior Mode', + 'eme.field.maxHealth': 'Max Health', + 'eme.field.movementSpeed': 'Movement Speed', + 'eme.field.followRange': 'Follow Range', + 'eme.field.scale': 'Scale', + 'eme.field.shadowRadius': 'Shadow Radius', + 'eme.field.visibleBoundsWidth': 'Visible Bounds Width', + 'eme.field.visibleBoundsHeight': 'Visible Bounds Height', + 'eme.field.visibleBoundsOffset': 'Visible Bounds Offset', + 'eme.field.animationMode': 'Animation Mode', + 'eme.field.swingSpeed': 'Swing Speed', + 'eme.field.walkSpeedMultiplier': 'Walk Speed Multiplier', + 'eme.section.host': 'Host', + 'eme.section.dimensions': 'Dimensions', + 'eme.section.movement': 'Movement', + 'eme.section.behavior': 'Behavior', + 'eme.section.attributes': 'Attributes', + 'eme.section.rendering': 'Rendering', + 'eme.section.animation': 'Animation', + 'eme.setting.enable_customization': 'Show advanced customization (Easy Model Entities)', + 'eme.setting.enable_experimental': 'Show experimental presets (Easy Model Entities)', + 'eme.preset.custom': 'Custom (manual settings)', + 'eme.preset.static': 'Static (no animation, no movement)', + 'eme.preset.statue': 'Statue (display, no movement)', + 'eme.preset.ticking': 'Ticking (server + client tick)', + 'eme.preset.animated': 'Animated (continuous animation)', + 'eme.preset.animated_randomly': 'Animated randomly (random idle bursts)', + 'eme.preset.humanoid_still': 'Humanoid (2 legs, standing)', + 'eme.preset.humanoid_wandering': 'Humanoid (2 legs, wandering)', + 'eme.preset.quadruped_still': 'Quadruped (4 legs, ground, standing)', + 'eme.preset.quadruped_wandering': 'Quadruped (4 legs, ground, wandering)', + 'eme.preset.aquatic_still': 'Aquatic (fish, still)', + 'eme.preset.aquatic_swimming': 'Aquatic (fish, swimming)', + 'eme.preset.winged_still': 'Winged (bird, perched)', + 'eme.preset.winged_wandering': 'Winged (bird, flying)', + 'eme.preset.winged_humanoid_still': 'Winged humanoid (standing)', + 'eme.preset.winged_humanoid_wandering': 'Winged humanoid (wandering)', + 'eme.preset.arthropod_still': 'Arthropod (insect/spider, still)', + 'eme.preset.arthropod_wandering': 'Arthropod (insect/spider, wandering)', + 'eme.preset.cuboid_still': 'Cuboid (block shape, still)', + 'eme.preset.cuboid_hopping': 'Cuboid (block shape, hopping)', + 'eme.preset.floating_still': 'Floating (hovering, still)', + 'eme.body.static': 'Static', + 'eme.body.biped': 'Biped (2 legs)', + 'eme.body.quadruped': 'Quadruped (4 legs)', + 'eme.body.aquatic': 'Aquatic', + 'eme.body.winged': 'Winged', + 'eme.body.winged_humanoid': 'Winged humanoid', + 'eme.body.arthropod': 'Arthropod', + 'eme.body.cuboid': 'Cuboid', + 'eme.body.floating': 'Floating', + 'eme.movement.ground': 'Ground', + 'eme.movement.static': 'Static', + 'eme.behavior.idle_only': 'Idle only', + 'eme.behavior.ambient': 'Ambient (wanders)', + 'eme.behavior.static': 'Static', + 'eme.behavior.external_owner': 'External owner', + 'eme.animation.automatic': 'Automatic', + 'eme.animation.random_idle': 'Random idle', + 'eme.animation.none': 'None', + 'eme.entity.ground_entity': 'Ground entity', + 'eme.entity.static_entity': 'Static entity', + 'eme.modelType.entity': 'Entity', + 'eme.modelType.block_entity': 'Block Entity', + 'eme.exportType.packs': 'Standalone: Data Pack + Resource Pack (ZIP)', + 'eme.exportType.mod_project': 'Standalone: write into mod project', + 'eme.exportType.model_only': 'Model only: mod integration (no data pack)' + }; + + const DE = { + 'eme.dialog.title': 'Easy Model Entities Export', + 'eme.field.preset': 'Vorlage', + 'eme.field.namespace': 'Namespace (Mod-ID)', + 'eme.field.profileId': 'Profil-ID', + 'eme.field.version': 'Version (optional)', + 'eme.field.targetVersion': 'Minecraft-Version', + 'eme.field.exportType': 'Export-Typ', + 'eme.field.modelType': 'Typ', + 'eme.field.customize': 'Einstellungen anpassen', + 'eme.field.hostEntityType': 'Host-Entität', + 'eme.field.movementType': 'Bewegungsart', + 'eme.field.bodyType': 'Körpertyp', + 'eme.field.width': 'Breite', + 'eme.field.height': 'Höhe', + 'eme.field.eyeHeight': 'Augenhöhe', + 'eme.field.speed': 'Geschwindigkeit', + 'eme.field.stepHeight': 'Schritthöhe', + 'eme.field.gravity': 'Schwerkraft', + 'eme.field.behaviorMode': 'Verhaltensmodus', + 'eme.field.maxHealth': 'Maximale Lebenspunkte', + 'eme.field.movementSpeed': 'Bewegungsgeschwindigkeit', + 'eme.field.followRange': 'Folgereichweite', + 'eme.field.scale': 'Skalierung', + 'eme.field.shadowRadius': 'Schattenradius', + 'eme.field.visibleBoundsWidth': 'Sichtbarkeitsbox-Breite', + 'eme.field.visibleBoundsHeight': 'Sichtbarkeitsbox-Höhe', + 'eme.field.visibleBoundsOffset': 'Sichtbarkeitsbox-Versatz', + 'eme.field.animationMode': 'Animationsmodus', + 'eme.field.swingSpeed': 'Schwunggeschwindigkeit', + 'eme.field.walkSpeedMultiplier': 'Geh-Geschwindigkeitsfaktor', + 'eme.section.host': 'Host', + 'eme.section.dimensions': 'Abmessungen', + 'eme.section.movement': 'Bewegung', + 'eme.section.behavior': 'Verhalten', + 'eme.section.attributes': 'Attribute', + 'eme.section.rendering': 'Darstellung', + 'eme.section.animation': 'Animation', + 'eme.setting.enable_customization': 'Erweiterte Anpassung anzeigen (Easy Model Entities)', + 'eme.setting.enable_experimental': 'Experimentelle Presets anzeigen (Easy Model Entities)', + 'eme.preset.custom': 'Benutzerdefiniert (manuelle Einstellungen)', + 'eme.preset.static': 'Statisch (keine Animation, keine Bewegung)', + 'eme.preset.statue': 'Statue (Anzeige, keine Bewegung)', + 'eme.preset.ticking': 'Tickend (Server- + Client-Tick)', + 'eme.preset.animated': 'Animiert (durchgehende Animation)', + 'eme.preset.animated_randomly': 'Zufällig animiert (zufällige Leerlauf-Schübe)', + 'eme.preset.humanoid_still': 'Humanoid (2 Beine, stehend)', + 'eme.preset.humanoid_wandering': 'Humanoid (2 Beine, umherlaufend)', + 'eme.preset.quadruped_still': 'Vierbeiner (4 Beine, Boden, stehend)', + 'eme.preset.quadruped_wandering': 'Vierbeiner (4 Beine, Boden, umherlaufend)', + 'eme.preset.aquatic_still': 'Wassertier (Fisch, ruhend)', + 'eme.preset.aquatic_swimming': 'Wassertier (Fisch, schwimmend)', + 'eme.preset.winged_still': 'Geflügelt (Vogel, sitzend)', + 'eme.preset.winged_wandering': 'Geflügelt (Vogel, fliegend)', + 'eme.preset.winged_humanoid_still': 'Geflügelter Humanoid (stehend)', + 'eme.preset.winged_humanoid_wandering': 'Geflügelter Humanoid (umherlaufend)', + 'eme.preset.arthropod_still': 'Gliederfüßer (Insekt/Spinne, ruhend)', + 'eme.preset.arthropod_wandering': 'Gliederfüßer (Insekt/Spinne, umherlaufend)', + 'eme.preset.cuboid_still': 'Quaderförmig (blockartig, ruhend)', + 'eme.preset.cuboid_hopping': 'Quaderförmig (blockartig, hüpfend)', + 'eme.preset.floating_still': 'Schwebend (ruhend)', + 'eme.body.static': 'Statisch', + 'eme.body.biped': 'Zweibeiner (2 Beine)', + 'eme.body.quadruped': 'Vierbeiner (4 Beine)', + 'eme.body.aquatic': 'Wassertier', + 'eme.body.winged': 'Geflügelt', + 'eme.body.winged_humanoid': 'Geflügelter Humanoid', + 'eme.body.arthropod': 'Gliederfüßer', + 'eme.body.cuboid': 'Quaderförmig', + 'eme.body.floating': 'Schwebend', + 'eme.movement.ground': 'Boden', + 'eme.movement.static': 'Statisch', + 'eme.behavior.idle_only': 'Nur Leerlauf', + 'eme.behavior.ambient': 'Umgebung (läuft umher)', + 'eme.behavior.static': 'Statisch', + 'eme.behavior.external_owner': 'Externer Besitzer', + 'eme.animation.automatic': 'Automatisch', + 'eme.animation.random_idle': 'Zufälliger Leerlauf', + 'eme.animation.none': 'Keine', + 'eme.entity.ground_entity': 'Boden-Entität', + 'eme.entity.static_entity': 'Statische Entität', + 'eme.modelType.entity': 'Entität', + 'eme.modelType.block_entity': 'Block-Entität', + 'eme.exportType.packs': 'Standalone: Data Pack + Resource Pack (ZIP)', + 'eme.exportType.mod_project': 'Standalone: in Mod-Projekt schreiben', + 'eme.exportType.model_only': 'Nur Modell: Mod-Integration (kein Data Pack)' + }; + + function registerTranslations() { + if (typeof Language !== 'undefined' && typeof Language.addTranslations + === 'function') { + Language.addTranslations('en', EN); + Language.addTranslations('de', DE); + } + } + +// Resolves a key via Blockbench's tl() when available, otherwise English. + function t(key) { + if (typeof tl === 'function') { + const translated = tl(key); + if (translated && translated !== key) { + return translated; + } + } + return EN[key] || key; + } + + module.exports = {EN, DE, registerTranslations, t}; + + /***/ + }, + + /***/ 763 + (module) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + class ModelDimensions { + static PIXELS_PER_BLOCK = 16; + static FALLBACK = { + width: 0.6, + height: 0.8, + eyeHeight: 0.5, + visibleBoundsWidth: 1.2, + visibleBoundsHeight: 1, + visibleBoundsOffset: [0, 0.5, 0] + }; + + static #EYE_HEIGHT_FACTOR = {biped: 0.9, quadruped: 0.8, static: 0.85}; + static #BOUNDS_MARGIN = 1.1; + + static #round(value, digits) { + const factor = Math.pow(10, digits == null ? 3 : digits); + return Math.round(value * factor) / factor; + } + + static #isUsableBounds(bounds) { + if (!bounds) { + return false; + } + + return ['minX', 'minY', 'minZ', 'maxX', 'maxY', 'maxZ'].every( + (key) => Number.isFinite(bounds[key])); + } + + static deriveDimensions(bounds, bodyType) { + if (!ModelDimensions.#isUsableBounds(bounds)) { + return { + ...ModelDimensions.FALLBACK, + visibleBoundsOffset: ModelDimensions.FALLBACK.visibleBoundsOffset.slice() + }; + } + + const extentX = Math.max(0, bounds.maxX - bounds.minX) + / ModelDimensions.PIXELS_PER_BLOCK; + const extentY = Math.max(0, bounds.maxY - bounds.minY) + / ModelDimensions.PIXELS_PER_BLOCK; + const extentZ = Math.max(0, bounds.maxZ - bounds.minZ) + / ModelDimensions.PIXELS_PER_BLOCK; + + const width = Math.max(extentX, extentZ); + const height = extentY; + if (width <= 0 || height <= 0) { + return { + ...ModelDimensions.FALLBACK, + visibleBoundsOffset: ModelDimensions.FALLBACK.visibleBoundsOffset.slice() + }; + } + + const eyeHeightFactor = + ModelDimensions.#EYE_HEIGHT_FACTOR[bodyType] == null + ? ModelDimensions.#EYE_HEIGHT_FACTOR.static + : ModelDimensions.#EYE_HEIGHT_FACTOR[bodyType]; + + return { + width: ModelDimensions.#round(width), + height: ModelDimensions.#round(height), + eyeHeight: ModelDimensions.#round(height * eyeHeightFactor), + visibleBoundsWidth: ModelDimensions.#round( + width * ModelDimensions.#BOUNDS_MARGIN), + visibleBoundsHeight: ModelDimensions.#round( + height * ModelDimensions.#BOUNDS_MARGIN), + visibleBoundsOffset: [0, ModelDimensions.#round(height / 2), 0] + }; + } + + // mutates settings in-place and returns it + static applyModelDimensions(settings, modelDimensions) { + if (!modelDimensions) { + return settings; + } + + settings.dimensions.width = modelDimensions.width; + settings.dimensions.height = modelDimensions.height; + settings.dimensions.eyeHeight = modelDimensions.eyeHeight; + settings.rendering.visibleBoundsWidth = modelDimensions.visibleBoundsWidth; + settings.rendering.visibleBoundsHeight = modelDimensions.visibleBoundsHeight; + settings.rendering.visibleBoundsOffset = modelDimensions.visibleBoundsOffset.slice(); + return settings; + } + } + + module.exports = {ModelDimensions}; + + /***/ + }, + + /***/ 858 + (module) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Suggests the most likely ModelPresetType from a model's bone names and bounds. +// Pure and side-effect free so it can be unit tested in isolation. Always +// suggests a "still" variant (the safe default); the user can switch to a +// moving variant in the dialog. + + class PresetDetector { + static #CUBE_RATIO = 1.4; + + static #names(stats) { + const boneNames = (stats && stats.boneNames) || []; + return boneNames.map((name) => String(name).toLowerCase()); + } + + static #any(names, ...needles) { + return names.some( + (name) => needles.some((needle) => name.includes(needle))); + } + + static #count(names, needle) { + return names.filter((name) => name.includes(needle)).length; + } + + static #extents(bounds) { + if (!bounds) { + return null; + } + const keys = ['minX', 'minY', 'minZ', 'maxX', 'maxY', 'maxZ']; + if (!keys.every((key) => Number.isFinite(bounds[key]))) { + return null; + } + return { + x: Math.max(0, bounds.maxX - bounds.minX), + y: Math.max(0, bounds.maxY - bounds.minY), + z: Math.max(0, bounds.maxZ - bounds.minZ) + }; + } + + // Returns {presetType, reason}. + static detect(stats, bounds) { + const names = PresetDetector.#names(stats); + const hasWings = PresetDetector.#any(names, 'wing'); + const hasArms = PresetDetector.#any(names, 'arm'); + const legCount = PresetDetector.#count(names, 'leg'); + const hasFrontBackLegs = names.some( + (name) => name.includes('leg') + && (name.includes('front') || name.includes('back'))); + + if (hasWings) { + return hasArms + ? {presetType: 'winged_humanoid_still', reason: 'wings + arms'} + : {presetType: 'winged_still', reason: 'wing bones'}; + } + + if (PresetDetector.#any(names, 'fin', 'fluke', 'fish')) { + return {presetType: 'aquatic_still', reason: 'fin/fish bones'}; + } + + if (legCount >= 6 || PresetDetector.#any(names, 'spider', 'insect', + 'mandible')) { + return {presetType: 'arthropod_still', reason: 'six or more legs'}; + } + + if (hasFrontBackLegs || legCount === 4) { + return { + presetType: 'quadruped_still', + reason: 'four legs (front/back)' + }; + } + + if (hasArms) { + return {presetType: 'humanoid_still', reason: 'arm bones'}; + } + + const extents = PresetDetector.#extents(bounds); + if (extents && extents.x > 0 && extents.y > 0 && extents.z > 0) { + const max = Math.max(extents.x, extents.y, extents.z); + const min = Math.min(extents.x, extents.y, extents.z); + if (max / min <= PresetDetector.#CUBE_RATIO) { + return { + presetType: 'cuboid_still', + reason: 'cube-like proportions' + }; + } + if (extents.y < Math.max(extents.x, extents.z) * 0.5) { + return {presetType: 'aquatic_still', reason: 'flat proportions'}; + } + } + + return {presetType: 'statue', reason: 'no distinguishing limbs'}; + } + + static detectPresetType(stats, bounds) { + return PresetDetector.detect(stats, bounds).presetType; + } + } + + module.exports = {PresetDetector}; + + /***/ + }, + + /***/ 229 + (module, __unused_webpack_exports, __webpack_require__) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + const {ResourceLocation} = __webpack_require__(20); + const { + PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + MODEL_TYPE_BLOCK_ENTITY + } = __webpack_require__(151); + + class Validator { + static BUDGETS = { + maxTextureSize: 2048, + maxModelFileSize: 2 * 1024 * 1024, + maxCubeCount: 512, + maxBoneCount: 128, + maxHierarchyDepth: 32 + }; + + static REQUIRED_BODY_PARTS = { + quadruped: ['root', 'body', 'head', 'front_left_leg', + 'front_right_leg', + 'back_left_leg', 'back_right_leg'], + biped: ['root', 'head', 'body', 'left_arm', 'right_arm', 'left_leg', + 'right_leg'], + static: [] + }; + + static #isFiniteNumber(value) { + return typeof value === 'number' && Number.isFinite(value); + } + + static #validateNumeric(errors, label, value) { + if (!Validator.#isFiniteNumber(value)) { + errors.push({ + code: 'INVALID_NUMERIC', + message: `${label} must be a finite number` + }); + return false; + } + return true; + } + + static validateSettings(settings, context) { + const errors = []; + const warnings = []; + const ctx = context || {}; + + if (!ResourceLocation.isValidNamespace(settings.namespace)) { + errors.push({ + code: 'INVALID_NAMESPACE', + message: `Invalid namespace: ${settings.namespace}` + }); + } + if (!ResourceLocation.isValidPath(settings.profileId)) { + errors.push({ + code: 'INVALID_PROFILE_ID', + message: `Invalid profile ID: ${settings.profileId}` + }); + } + + const id = `${settings.namespace}:${settings.profileId}`; + if ( + ResourceLocation.isValidNamespace(settings.namespace) && + ResourceLocation.isValidPath(settings.profileId) && + !ResourceLocation.isValidResourceLocation(id) + ) { + errors.push({ + code: 'INVALID_RESOURCE_LOCATION', + message: `Invalid resource location: ${id}` + }); + } + const blockEntity = settings.modelType === MODEL_TYPE_BLOCK_ENTITY; + + // Block entity host type is derived by the mod; only entities carry one. + if (!blockEntity + && !ResourceLocation.isValidResourceLocation( + settings.host.entityType)) { + errors.push({ + code: 'INVALID_RESOURCE_LOCATION', + message: `Invalid host entity type: ${settings.host.entityType}` + }); + } + const validPresets = blockEntity ? BLOCK_ENTITY_PRESET_TYPES + : PRESET_TYPES; + if (!validPresets.includes(settings.presetType)) { + errors.push({ + code: 'INVALID_PRESET_TYPE', + message: `Invalid preset type: ${settings.presetType}` + }); + } + + const {width, height, eyeHeight} = settings.dimensions; + const widthValid = Validator.#validateNumeric(errors, + 'dimensions.width', + width); + const heightValid = Validator.#validateNumeric(errors, + 'dimensions.height', + height); + const eyeValid = Validator.#validateNumeric(errors, + 'dimensions.eye_height', + eyeHeight); + if (widthValid && width <= 0) { + errors.push({ + code: 'INVALID_DIMENSIONS', + message: 'width must be greater than 0' + }); + } + if (heightValid && height <= 0) { + errors.push({ + code: 'INVALID_DIMENSIONS', + message: 'height must be greater than 0' + }); + } + if (eyeValid && eyeHeight < 0) { + errors.push({ + code: 'INVALID_DIMENSIONS', + message: 'eye_height must not be negative' + }); + } + if (eyeValid && heightValid && eyeHeight > height) { + errors.push({ + code: 'INVALID_DIMENSIONS', + message: 'eye_height must not exceed height' + }); + } + + Validator.#validateNumeric(errors, 'movement.speed', + settings.movement.speed); + Validator.#validateNumeric(errors, 'movement.step_height', + settings.movement.stepHeight); + + if (ctx.hasModel === false) { + errors.push( + { + code: 'MISSING_MODEL', + message: 'No model present in project' + }); + } + if (ctx.hasTexture === false) { + errors.push( + { + code: 'MISSING_TEXTURE', + message: 'No texture present in project' + }); + } + + if (Validator.#isFiniteNumber(ctx.textureCount) && ctx.textureCount + > 1) { + warnings.push({ + code: 'MULTIPLE_TEXTURES', + message: 'Project has multiple textures; only the active texture is exported' + }); + } + + if (Validator.#isFiniteNumber(ctx.textureWidth) + && Validator.#isFiniteNumber(ctx.textureHeight)) { + if (ctx.textureWidth > Validator.BUDGETS.maxTextureSize + || ctx.textureHeight > Validator.BUDGETS.maxTextureSize) { + warnings.push({ + code: 'LARGE_TEXTURE', + message: `Texture larger than ${Validator.BUDGETS.maxTextureSize}x${Validator.BUDGETS.maxTextureSize}` + }); + } + } + if (Validator.#isFiniteNumber(ctx.modelFileSize) && ctx.modelFileSize + > Validator.BUDGETS.maxModelFileSize) { + warnings.push( + {code: 'LARGE_MODEL', message: 'Model file larger than 2 MB'}); + } + if (Validator.#isFiniteNumber(ctx.cubeCount) && ctx.cubeCount + > Validator.BUDGETS.maxCubeCount) { + warnings.push({ + code: 'HIGH_CUBE_COUNT', + message: `More than ${Validator.BUDGETS.maxCubeCount} cubes` + }); + } + if (Validator.#isFiniteNumber(ctx.boneCount) && ctx.boneCount + > Validator.BUDGETS.maxBoneCount) { + warnings.push({ + code: 'HIGH_BONE_COUNT', + message: `More than ${Validator.BUDGETS.maxBoneCount} bones` + }); + } + if (Validator.#isFiniteNumber(ctx.hierarchyDepth) + && ctx.hierarchyDepth + > Validator.BUDGETS.maxHierarchyDepth) { + warnings.push({ + code: 'DEEP_HIERARCHY', + message: `Hierarchy deeper than ${Validator.BUDGETS.maxHierarchyDepth}` + }); + } + + const missingParts = Validator.getMissingBodyParts( + settings.host.bodyType, + ctx.boneNames || []); + missingParts.forEach((part) => { + warnings.push({ + code: 'MISSING_BODY_PART', + message: `Missing recommended body part: ${part}` + }); + }); + + if (ctx.visibleBoundsManual === false) { + warnings.push({ + code: 'VISIBLE_BOUNDS_DEFAULT', + message: 'Visible bounds were not set manually' + }); + } + + return {errors, warnings, valid: errors.length === 0}; + } + + static getMissingBodyParts(bodyType, boneNames) { + const required = Validator.REQUIRED_BODY_PARTS[bodyType] || []; + const present = new Set( + (boneNames || []).map((name) => String(name).toLowerCase())); + return required.filter((part) => !present.has(part)); + } + + static validateOutputPath(outputRoot, relativePath) { + if (typeof relativePath !== 'string' || relativePath.length === 0) { + return { + valid: false, + code: 'INVALID_OUTPUT_PATH', + message: 'Empty output path' + }; + } + const normalized = relativePath.replaceAll('\\', '/'); + if (normalized.includes('..')) { + return { + valid: false, + code: 'PATH_TRAVERSAL', + message: 'Path traversal detected' + }; + } + if (/^([a-zA-Z]:\/|\/)/.test(normalized)) { + return { + valid: false, + code: 'ABSOLUTE_PATH', + message: 'Absolute paths are not allowed' + }; + } + return {valid: true, root: outputRoot, path: normalized}; + } + } + + module.exports = {Validator}; + + /***/ + }, + + /***/ 151 + (module) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Mirrors de.markusbordihn.easymodelentities.profile.ModelPresetType and the +// per-preset defaults derived by EasyModelProfileParser / ModelRenderProfileParser. +// This file is the single source of truth the plugin uses to compute minimal +// (diff-based) profiles, so it must stay in sync with the mod. + + const SCHEMA_VERSION = '0.1.0'; + +// Mirrors de.markusbordihn.easymodelentities.data.profile.ModelType. + const MODEL_TYPE_ENTITY = 'entity'; + const MODEL_TYPE_BLOCK_ENTITY = 'block_entity'; + const MODEL_TYPES = [MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY]; + + const GROUND_ENTITY = 'easy_model_entities:ground_entity'; + const STATIC_ENTITY = 'easy_model_entities:static_entity'; + + const FALLBACK_DIMENSIONS = {width: 0.6, height: 1.8, eyeHeight: 1.62}; + const BLOCK_ENTITY_DIMENSIONS = {width: 1.0, height: 1.0, eyeHeight: 0.5}; + +// All ModelPresetType values in serialized form. + const PRESET_TYPES = [ + 'custom', + 'static', + 'statue', + 'humanoid_still', + 'humanoid_wandering', + 'quadruped_still', + 'quadruped_wandering', + 'aquatic_still', + 'aquatic_swimming', + 'winged_still', + 'winged_wandering', + 'winged_humanoid_still', + 'winged_humanoid_wandering', + 'arthropod_still', + 'arthropod_wandering', + 'cuboid_still', + 'cuboid_hopping', + 'floating_still' + ]; + +// All ModelBlockEntityPresetType values in serialized form. + const BLOCK_ENTITY_PRESET_TYPES = [ + 'static', + 'ticking', + 'animated', + 'animated_randomly' + ]; + +// Presets the mod marks as stable for pack authors; experimental ones are hidden +// in the UI behind the "Show experimental presets" setting. + const STABLE_PRESET_TYPES = new Set([ + 'static', + 'statue', + 'humanoid_still', + 'humanoid_wandering', + 'quadruped_still', + 'quadruped_wandering', + // Block entity stable presets. + 'animated', + 'animated_randomly' + ]); + +// Presets selectable as a starting point in the UI (custom handled separately). + const SELECTABLE_PRESET_TYPES = PRESET_TYPES.filter( + (id) => id !== 'custom'); + + function isStablePreset(presetType) { + return STABLE_PRESET_TYPES.has(presetType); + } + + function isCustom(presetType) { + return presetType === 'custom'; + } + + function isStill(presetType) { + return String(presetType).endsWith('_still'); + } + + function isMoving(presetType) { + return String(presetType).endsWith('_wandering') + || presetType === 'aquatic_swimming' + || presetType === 'cuboid_hopping'; + } + + function bodyType(presetType) { + switch (presetType) { + case 'humanoid_still': + case 'humanoid_wandering': + return 'biped'; + case 'quadruped_still': + case 'quadruped_wandering': + return 'quadruped'; + case 'aquatic_still': + case 'aquatic_swimming': + return 'aquatic'; + case 'winged_still': + case 'winged_wandering': + return 'winged'; + case 'winged_humanoid_still': + case 'winged_humanoid_wandering': + return 'winged_humanoid'; + case 'arthropod_still': + case 'arthropod_wandering': + return 'arthropod'; + case 'cuboid_still': + case 'cuboid_hopping': + return 'cuboid'; + case 'floating_still': + return 'floating'; + default: + return 'static'; + } + } + + function movementType(presetType) { + return isMoving(presetType) ? 'ground' : 'static'; + } + + function entityType(presetType) { + return isMoving(presetType) || presetType === 'static' ? GROUND_ENTITY + : STATIC_ENTITY; + } + + function presetDimensions(presetType) { + switch (presetType) { + case 'quadruped_still': + case 'quadruped_wandering': + return {width: 0.9, height: 0.9, eyeHeight: 0.6}; + case 'aquatic_still': + case 'aquatic_swimming': + return {width: 0.7, height: 0.4, eyeHeight: 0.25}; + case 'winged_still': + case 'winged_wandering': + return {width: 0.6, height: 0.9, eyeHeight: 0.6}; + case 'winged_humanoid_still': + case 'winged_humanoid_wandering': + return {width: 0.6, height: 0.8, eyeHeight: 0.6}; + case 'arthropod_still': + case 'arthropod_wandering': + return {width: 1.4, height: 0.9, eyeHeight: 0.45}; + case 'cuboid_still': + case 'cuboid_hopping': + case 'floating_still': + return {width: 1.0, height: 1.0, eyeHeight: 0.5}; + default: + return {...FALLBACK_DIMENSIONS}; + } + } + +// [visibleBoundsWidth, visibleBoundsHeight, visibleBoundsOffsetY, shadowRadius] + function presetRenderBounds(presetType) { + switch (presetType) { + case 'quadruped_still': + case 'quadruped_wandering': + return [0.9, 0.9, 0.45, 0.45]; + case 'aquatic_still': + case 'aquatic_swimming': + return [0.7, 0.4, 0.2, 0.25]; + case 'winged_still': + case 'winged_wandering': + return [0.6, 0.9, 0.45, 0.25]; + case 'winged_humanoid_still': + case 'winged_humanoid_wandering': + return [0.6, 0.8, 0.4, 0.25]; + case 'arthropod_still': + case 'arthropod_wandering': + return [1.4, 0.9, 0.45, 0.7]; + case 'cuboid_still': + case 'cuboid_hopping': + case 'floating_still': + return [1.0, 1.0, 0.5, 0.5]; + default: + return [0.6, 1.8, 0.9, 0.3]; + } + } + +// Behavior mode the mod derives for a preset, given the resolved movement type +// (only relevant for the custom preset where movement type can vary). + function behaviorModeFor(presetType, move) { + if (isMoving(presetType)) { + return 'ambient'; + } + if (isStill(presetType)) { + return 'idle_only'; + } + if (presetType === 'custom') { + return move === 'ground' ? 'idle_only' : 'static'; + } + return 'static'; + } + + function behaviorMode(presetType) { + return behaviorModeFor(presetType, movementType(presetType)); + } + +// Movement defaults the mod derives from the resolved movement type. + function movementDefaults(presetType, move) { + const ground = move === 'ground'; + return { + speed: ground ? 0.22 : 0, + stepHeight: ground ? 0.6 : 0, + gravity: presetType === 'static' || ground + }; + } + + function animationMode(presetType) { + return presetType === 'custom' || presetType === 'static' + || presetType === 'statue' ? 'none' : 'automatic'; + } + +// Animation mode the mod derives for a block entity preset. + function blockEntityAnimationMode(presetType) { + switch (presetType) { + case 'animated': + return 'automatic'; + case 'animated_randomly': + return 'random_idle'; + default: + return 'none'; + } + } + +// Block entities ignore movement/behavior/attributes; the host is always a +// static cuboid block. Render bounds default to a 1x1x1 block. + function blockEntityPresetDefaults(presetType) { + return { + schemaVersion: SCHEMA_VERSION, + modelType: MODEL_TYPE_BLOCK_ENTITY, + presetType: presetType, + version: '', + host: {entityType: '', movementType: 'static', bodyType: 'static'}, + dimensions: {...BLOCK_ENTITY_DIMENSIONS}, + movement: {speed: 0, stepHeight: 0, gravity: false}, + behavior: {mode: 'static', lookAtPlayers: false, randomStroll: false}, + attributes: {maxHealth: 10, movementSpeed: 0, followRange: 16}, + rendering: { + scale: 1, + shadowRadius: 0.5, + visibleBoundsWidth: 1.0, + visibleBoundsHeight: 1.0, + visibleBoundsOffset: [0, 0.5, 0] + }, + animation: { + mode: blockEntityAnimationMode(presetType), + swingSpeed: 1, + walkSpeedMultiplier: 1 + } + }; + } + +// Full settings object matching the mod defaults for the given preset type. + function presetDefaults(presetType, modelType) { + if (modelType === MODEL_TYPE_BLOCK_ENTITY) { + return blockEntityPresetDefaults(presetType); + } + const move = movementType(presetType); + const ground = move === 'ground'; + const speed = ground ? 0.22 : 0; + const stepHeight = ground ? 0.6 : 0; + const gravity = presetType === 'static' || ground; + const mode = behaviorMode(presetType); + const lookAtPlayers = mode === 'idle_only' || mode === 'ambient'; + const randomStroll = ground && mode === 'ambient'; + const dimensions = presetDimensions(presetType); + const [boundsWidth, boundsHeight, boundsOffsetY, shadowRadius] = + presetRenderBounds(presetType); + + return { + schemaVersion: SCHEMA_VERSION, + modelType: MODEL_TYPE_ENTITY, + presetType: presetType, + version: '', + host: { + entityType: entityType(presetType), + movementType: move, + bodyType: bodyType(presetType) + }, + dimensions: dimensions, + movement: {speed: speed, stepHeight: stepHeight, gravity: gravity}, + behavior: { + mode: mode, + lookAtPlayers: lookAtPlayers, + randomStroll: randomStroll + }, + attributes: {maxHealth: 10, movementSpeed: speed, followRange: 16}, + rendering: { + scale: 1, + shadowRadius: shadowRadius, + visibleBoundsWidth: boundsWidth, + visibleBoundsHeight: boundsHeight, + visibleBoundsOffset: [0, boundsOffsetY, 0] + }, + animation: { + mode: animationMode(presetType), + swingSpeed: 1, + walkSpeedMultiplier: 1 + } + }; + } + + module.exports = { + SCHEMA_VERSION, + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY, + MODEL_TYPES, + GROUND_ENTITY, + STATIC_ENTITY, + PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + SELECTABLE_PRESET_TYPES, + STABLE_PRESET_TYPES, + isStablePreset, + isCustom, + isStill, + isMoving, + bodyType, + movementType, + entityType, + behaviorMode, + behaviorModeFor, + movementDefaults, + animationMode, + blockEntityAnimationMode, + presetDimensions, + presetRenderBounds, + presetDefaults, + blockEntityPresetDefaults + }; + + /***/ + }, + + /***/ 668 + (module, __unused_webpack_exports, __webpack_require__) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + const { + SCHEMA_VERSION, + PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + SELECTABLE_PRESET_TYPES, + MODEL_TYPE_ENTITY, + presetDefaults + } = __webpack_require__(151); + + const DEFAULT_PRESET = 'statue'; + + function deepMerge(target, source) { + const result = Array.isArray(target) ? target.slice() : ({ + ...target + }); + Object.keys(source).forEach((key) => { + const value = source[key]; + if (value && typeof value === 'object' && !Array.isArray(value)) { + result[key] = deepMerge(result[key] || {}, value); + } else if (Array.isArray(value)) { + result[key] = value.slice(); + } else { + result[key] = value; + } + }); + return result; + } + + function getDefaults() { + return deepMerge(presetDefaults(DEFAULT_PRESET), { + namespace: 'example_org', + profileId: 'entity', + targetVersion: '1.20.1' + }); + } + +// Full settings object for a preset type, including the identity fields. The +// model type disambiguates presets that exist for both entities and block +// entities (e.g. "static"). + function applyTemplate(presetType, modelType) { + return deepMerge(getDefaults(), + presetDefaults(presetType, modelType || MODEL_TYPE_ENTITY)); + } + + module.exports = { + SCHEMA_VERSION, + DEFAULT_PRESET, + PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + SELECTABLE_PRESET_TYPES, + getDefaults, + applyTemplate, + presetDefaults, + deepMerge + }; + + /***/ + }, + + /***/ 954 + (module) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + const DEFAULT_VERSION_ID = '1.20.1'; + + const VERSIONS = [ + { + id: '1.20.1', + label: 'Minecraft 1.20.1', + dataFormat: 15, + resourceFormat: 15, + enabled: true + }, + { + id: '1.21.1', + label: 'Minecraft 1.21.1', + dataFormat: 48, + resourceFormat: 34, + enabled: false + }, + { + id: '1.21.11', + label: 'Minecraft 1.21.11', + dataFormat: null, + resourceFormat: null, + enabled: false + }, + { + id: '26.1.2', + label: 'Minecraft 26.1.2', + dataFormat: null, + resourceFormat: null, + enabled: false + } + ]; + + function getVersions() { + return VERSIONS.map((version) => ({...version})); + } + + function getVersion(id) { + return VERSIONS.find((version) => version.id === id) || null; + } + + function getEnabledVersions() { + return getVersions().filter((version) => version.enabled); + } + + function getDefaultVersionId() { + return DEFAULT_VERSION_ID; + } + + function getPackFormats(id) { + const version = getVersion(id); + if (!version?.enabled) { + return null; + } + return { + dataFormat: version.dataFormat, + resourceFormat: version.resourceFormat + }; + } + + module.exports = { + getVersions, + getEnabledVersions, + getDefaultVersionId, + getPackFormats + }; + + /***/ + }, + + /***/ 924 + (module, __unused_webpack_exports, __webpack_require__) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + const {getVersions} = __webpack_require__(954); + const {getDefaults, applyTemplate, deepMerge} = __webpack_require__(668); + const { + SELECTABLE_PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + isStablePreset, + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY + } = __webpack_require__(151); + const {ModelDimensions} = __webpack_require__(763); + const {t} = __webpack_require__(16); + + function presetLabel(id) { + return id === 'custom' ? t('eme.preset.custom') : t(`eme.preset.${id}`); + } + + function entityPresetIds(experimental) { + return experimental + ? ['custom', ...SELECTABLE_PRESET_TYPES] + : SELECTABLE_PRESET_TYPES.filter(isStablePreset); + } + + function blockPresetIds(experimental) { + return experimental + ? BLOCK_ENTITY_PRESET_TYPES.slice() + : BLOCK_ENTITY_PRESET_TYPES.filter(isStablePreset); + } + +// Experimental (WIP) presets are hidden unless the experimental setting is on. +// `ensure` keeps an already-selected preset visible even when experimental is +// off, so reopening a project that used one never breaks the dropdown. + function presetOptions(modelType, experimental, ensure) { + const ids = modelType === MODEL_TYPE_BLOCK_ENTITY + ? blockPresetIds(experimental) : entityPresetIds(experimental); + if (ensure && !ids.includes(ensure)) { + ids.push(ensure); + } + const options = {}; + ids.forEach((id) => { + options[id] = presetLabel(id); + }); + return options; + } + + function modelTypeOptions() { + return { + [MODEL_TYPE_ENTITY]: t('eme.modelType.entity'), + [MODEL_TYPE_BLOCK_ENTITY]: t('eme.modelType.block_entity') + }; + } + + function exportTypeOptions() { + return { + packs: t('eme.exportType.packs'), + mod_project: t('eme.exportType.mod_project'), + model_only: t('eme.exportType.model_only') + }; + } + + function hostEntityTypeOptions() { + return { + 'easy_model_entities:ground_entity': t('eme.entity.ground_entity'), + 'easy_model_entities:static_entity': t('eme.entity.static_entity') + }; + } + + function movementTypeOptions() { + return { + ground: t('eme.movement.ground'), + static: t('eme.movement.static') + }; + } + + function bodyTypeOptions() { + return { + static: t('eme.body.static'), + biped: t('eme.body.biped'), + quadruped: t('eme.body.quadruped'), + aquatic: t('eme.body.aquatic'), + winged: t('eme.body.winged'), + winged_humanoid: t('eme.body.winged_humanoid'), + arthropod: t('eme.body.arthropod'), + cuboid: t('eme.body.cuboid'), + floating: t('eme.body.floating') + }; + } + + function behaviorModeOptions() { + return { + idle_only: t('eme.behavior.idle_only'), + ambient: t('eme.behavior.ambient'), + static: t('eme.behavior.static'), + external_owner: t('eme.behavior.external_owner') + }; + } + + function animationModeOptions() { + return { + automatic: t('eme.animation.automatic'), + random_idle: t('eme.animation.random_idle'), + none: t('eme.animation.none') + }; + } + + function versionOptions() { + const options = {}; + getVersions().forEach((version) => { + options[version.id] = version.enabled ? version.label + : `${version.label} (coming soon)`; + }); + return options; + } + + function settingsToForm(settings) { + return { + namespace: settings.namespace, + profileId: settings.profileId, + version: settings.version || '', + targetVersion: settings.targetVersion, + hostEntityType: settings.host.entityType, + movementType: settings.host.movementType, + bodyType: settings.host.bodyType, + width: settings.dimensions.width, + height: settings.dimensions.height, + eyeHeight: settings.dimensions.eyeHeight, + speed: settings.movement.speed, + stepHeight: settings.movement.stepHeight, + gravity: settings.movement.gravity, + behaviorMode: settings.behavior.mode, + maxHealth: settings.attributes.maxHealth, + movementSpeed: settings.attributes.movementSpeed, + followRange: settings.attributes.followRange, + scale: settings.rendering.scale, + shadowRadius: settings.rendering.shadowRadius, + visibleBoundsWidth: settings.rendering.visibleBoundsWidth, + visibleBoundsHeight: settings.rendering.visibleBoundsHeight, + visibleBoundsOffset: settings.rendering.visibleBoundsOffset.slice(), + animationMode: settings.animation.mode, + swingSpeed: settings.animation.swingSpeed, + walkSpeedMultiplier: settings.animation.walkSpeedMultiplier + }; + } + + function formToSettings(form, base) { + return { + schemaVersion: base.schemaVersion, + modelType: base.modelType, + presetType: base.presetType, + version: base.version || '', + namespace: String(form.namespace || '').trim(), + profileId: String(form.profileId || '').trim(), + targetVersion: base.targetVersion, + host: { + entityType: form.hostEntityType, + movementType: form.movementType, + bodyType: form.bodyType + }, + dimensions: { + width: Number(form.width), + height: Number(form.height), + eyeHeight: Number(form.eyeHeight) + }, + movement: { + speed: Number(form.speed), + stepHeight: Number(form.stepHeight), + gravity: !!form.gravity + }, + behavior: { + mode: form.behaviorMode, + lookAtPlayers: base.behavior ? base.behavior.lookAtPlayers : true, + randomStroll: base.behavior ? base.behavior.randomStroll : false + }, + attributes: { + maxHealth: Number(form.maxHealth), + movementSpeed: Number(form.movementSpeed), + followRange: Number(form.followRange) + }, + rendering: { + scale: Number(form.scale), + shadowRadius: Number(form.shadowRadius), + visibleBoundsWidth: Number(form.visibleBoundsWidth), + visibleBoundsHeight: Number(form.visibleBoundsHeight), + visibleBoundsOffset: (form.visibleBoundsOffset || [0, 0, 0]).map( + Number) + }, + animation: { + mode: form.animationMode, + swingSpeed: Number(form.swingSpeed), + walkSpeedMultiplier: Number(form.walkSpeedMultiplier) + } + }; + } + +// Active preset depends on whether a block entity datapack is being authored. +// Model-only export still authors a render (body) preset, so it uses the entity +// preset list. + function activeModelType(form) { + return form.exportType === 'model_only' + ? MODEL_TYPE_ENTITY : (form.modelType || MODEL_TYPE_ENTITY); + } + + function activePreset(form) { + return activeModelType(form) === MODEL_TYPE_BLOCK_ENTITY + ? (form.blockPreset || 'static') : (form.preset || 'custom'); + } + + function presetFormValues(presetType, modelType, modelDimensions) { + const settings = ModelDimensions.applyModelDimensions( + applyTemplate(presetType, modelType), modelDimensions); + const form = settingsToForm(settings); + delete form.namespace; + delete form.profileId; + delete form.targetVersion; + delete form.version; + return form; + } + + function resolveExportSettings(form, base, modelDimensions) { + const exportType = form.exportType || 'packs'; + const modelOnly = exportType === 'model_only'; + const modelType = activeModelType(form); + const blockEntity = modelType === MODEL_TYPE_BLOCK_ENTITY; + const preset = activePreset(form); + + let settings; + if (!blockEntity && preset === 'custom') { + settings = deepMerge(getDefaults(), base); + } else { + settings = ModelDimensions.applyModelDimensions( + applyTemplate(preset, modelType), modelDimensions); + } + + settings.schemaVersion = base.schemaVersion || settings.schemaVersion; + settings.modelType = modelType; + settings.presetType = preset; + settings.namespace = String(form.namespace || '').trim(); + settings.profileId = String(form.profileId || '').trim(); + settings.version = String(form.version || '').trim(); + settings.targetVersion = form.targetVersion; + + if (form.customize) { + settings = formToSettings(form, settings); + } + // Remembered so a customized project reopens with its values (and target). + settings.customize = !!form.customize; + settings.exportType = exportType; + settings.exportTarget = exportType === 'packs' ? 'packs' + : 'mod_project'; + settings.modelOnly = modelOnly; + return settings; + } + + function advancedField(field, showCustomize) { + return Object.assign({condition: showCustomize}, field); + } + + function buildFormConfig(settings, ui) { + const values = settingsToForm(settings); + const state = ui || {}; + const allowCustomize = !!state.showCustomization; + + const standalone = (form) => form.exportType !== 'model_only'; + const blockEntitySelected = (form) => + standalone(form) && form.modelType === MODEL_TYPE_BLOCK_ENTITY; + const customizeOn = (form) => allowCustomize && !!form.customize; + // Server-profile advanced fields only make sense for a standalone datapack. + const showServer = (form) => customizeOn(form) && standalone(form); + // Host/movement/behavior/attributes only apply to standalone entities. + const showEntity = (form) => showServer(form) && !blockEntitySelected( + form); + // Render-profile advanced fields apply to every export kind. + const showRender = customizeOn; + + const experimental = !!state.experimental; + const entityValue = state.preset || 'statue'; + const blockValue = state.blockPreset || 'static'; + + const config = { + exportType: { + label: t('eme.field.exportType'), + type: 'select', + options: exportTypeOptions(), + value: state.exportType || 'packs' + }, + modelType: { + label: t('eme.field.modelType'), + type: 'select', + options: modelTypeOptions(), + value: state.modelType || MODEL_TYPE_ENTITY, + condition: standalone + }, + preset: { + label: t('eme.field.preset'), + type: 'select', + options: presetOptions(MODEL_TYPE_ENTITY, experimental, + entityValue), + value: entityValue, + condition: (form) => !blockEntitySelected(form) + }, + blockPreset: { + label: t('eme.field.preset'), + type: 'select', + options: presetOptions(MODEL_TYPE_BLOCK_ENTITY, experimental, + blockValue), + value: blockValue, + condition: blockEntitySelected + }, + namespace: { + label: t('eme.field.namespace'), + type: 'text', + value: values.namespace, + placeholder: 'your_mod_id' + }, + profileId: { + label: t('eme.field.profileId'), type: 'text', + value: values.profileId + }, + version: { + label: t('eme.field.version'), + type: 'text', + value: values.version, + placeholder: 'v1' + }, + targetVersion: { + label: t('eme.field.targetVersion'), + type: 'select', + options: versionOptions(), + value: values.targetVersion + } + }; + + if (allowCustomize) { + config.customize = { + label: t('eme.field.customize'), + type: 'checkbox', + value: !!state.customize + }; + } + + Object.assign(config, { + host_header: advancedField( + {type: 'info', text: `### ${t('eme.section.host')}`}, showEntity), + hostEntityType: advancedField({ + label: t('eme.field.hostEntityType'), + type: 'select', + options: hostEntityTypeOptions(), + value: values.hostEntityType + }, showEntity), + movementType: advancedField({ + label: t('eme.field.movementType'), + type: 'select', + options: movementTypeOptions(), + value: values.movementType + }, showEntity), + bodyType: advancedField({ + label: t('eme.field.bodyType'), + type: 'select', + options: bodyTypeOptions(), + value: values.bodyType + }, showEntity), + + dimensions_header: advancedField( + {type: 'info', text: `### ${t('eme.section.dimensions')}`}, + showServer), + width: advancedField( + { + label: t('eme.field.width'), + type: 'number', + value: values.width, + step: 0.1 + }, showServer), + height: advancedField( + { + label: t('eme.field.height'), + type: 'number', + value: values.height, + step: 0.1 + }, showServer), + eyeHeight: advancedField( + { + label: t('eme.field.eyeHeight'), type: 'number', + value: values.eyeHeight, step: 0.1 + }, showServer), + + movement_header: advancedField( + {type: 'info', text: `### ${t('eme.section.movement')}`}, + showEntity), + speed: advancedField( + { + label: t('eme.field.speed'), + type: 'number', + value: values.speed, + step: 0.01 + }, showEntity), + stepHeight: advancedField( + { + label: t('eme.field.stepHeight'), type: 'number', + value: values.stepHeight, step: 0.1 + }, showEntity), + gravity: advancedField( + { + label: t('eme.field.gravity'), type: 'checkbox', + value: values.gravity + }, showEntity), + + behavior_header: advancedField( + {type: 'info', text: `### ${t('eme.section.behavior')}`}, + showEntity), + behaviorMode: advancedField({ + label: t('eme.field.behaviorMode'), + type: 'select', + options: behaviorModeOptions(), + value: values.behaviorMode + }, showEntity), + + attributes_header: advancedField( + {type: 'info', text: `### ${t('eme.section.attributes')}`}, + showEntity), + maxHealth: advancedField( + { + label: t('eme.field.maxHealth'), type: 'number', + value: values.maxHealth, step: 0.5 + }, showEntity), + movementSpeed: advancedField( + { + label: t('eme.field.movementSpeed'), type: 'number', + value: values.movementSpeed, step: 0.01 + }, showEntity), + followRange: advancedField( + { + label: t('eme.field.followRange'), type: 'number', + value: values.followRange, step: 1 + }, showEntity), + + rendering_header: advancedField( + {type: 'info', text: `### ${t('eme.section.rendering')}`}, + showRender), + scale: advancedField( + { + label: t('eme.field.scale'), + type: 'number', + value: values.scale, + step: 0.1 + }, showRender), + shadowRadius: advancedField( + { + label: t('eme.field.shadowRadius'), type: 'number', + value: values.shadowRadius, step: 0.1 + }, showRender), + visibleBoundsWidth: advancedField( + { + label: t('eme.field.visibleBoundsWidth'), type: 'number', + value: values.visibleBoundsWidth, step: 0.1 + }, showRender), + visibleBoundsHeight: advancedField( + { + label: t('eme.field.visibleBoundsHeight'), type: 'number', + value: values.visibleBoundsHeight, step: 0.1 + }, showRender), + visibleBoundsOffset: advancedField( + { + label: t('eme.field.visibleBoundsOffset'), type: 'vector', + value: values.visibleBoundsOffset + }, showRender), + + animation_header: advancedField( + {type: 'info', text: `### ${t('eme.section.animation')}`}, + showRender), + animationMode: advancedField({ + label: t('eme.field.animationMode'), + type: 'select', + options: animationModeOptions(), + value: values.animationMode + }, showRender), + swingSpeed: advancedField( + { + label: t('eme.field.swingSpeed'), type: 'number', + value: values.swingSpeed, step: 0.1 + }, showRender), + walkSpeedMultiplier: advancedField( + { + label: t('eme.field.walkSpeedMultiplier'), type: 'number', + value: values.walkSpeedMultiplier, step: 0.1 + }, showRender) + }); + + return config; + } + + function openExportDialog(options) { + const settings = options.settings; + const modelDimensions = options.modelDimensions; + // Tracks the active model type + preset so advanced fields refill whenever the + // user switches between entity/block entity or picks a different preset. + let lastKey = `${activeModelType(options)}|${activePreset(options)}`; + + const dialog = new Dialog({ + id: 'eme_export_dialog', + title: t('eme.dialog.title'), + width: 560, + form: buildFormConfig(settings, { + preset: options.preset, + blockPreset: options.blockPreset, + modelType: options.modelType, + exportType: options.exportType, + customize: options.customize, + showCustomization: options.showCustomization, + experimental: options.experimental + }), + onFormChange(form) { + const modelType = activeModelType(form); + const preset = activePreset(form); + const key = `${modelType}|${preset}`; + if (key !== lastKey) { + lastKey = key; + const isCustomEntity = modelType === MODEL_TYPE_ENTITY + && preset === 'custom'; + if (!isCustomEntity) { + dialog.setFormValues( + presetFormValues(preset, modelType, modelDimensions), + false); + } + } + }, + onConfirm(form) { + const finalSettings = resolveExportSettings(form, settings, + modelDimensions); + // Records that experimental presets were available for this export. + finalSettings.experimental = !!options.experimental; + options.onExport(finalSettings, finalSettings.exportTarget); + } + }); + dialog.show(); + return dialog; + } + + module.exports = { + presetOptions, + settingsToForm, + formToSettings, + presetFormValues, + resolveExportSettings, + openExportDialog + }; + + /***/ + }, + + /***/ 20 + (module) { + + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + class ResourceLocation { + static #NAMESPACE_PATTERN = /^[a-z0-9_.-]+$/; + static #PATH_PATTERN = /^[a-z0-9_./-]+$/; + + static isValidNamespace(namespace) { + return typeof namespace === 'string' + && ResourceLocation.#NAMESPACE_PATTERN.test(namespace); + } + + static isValidPath(path) { + return typeof path === 'string' + && ResourceLocation.#PATH_PATTERN.test(path) + && !path.includes('..'); + } + + static isValidResourceLocation(value) { + if (typeof value !== 'string' || !value.includes(':')) { + return false; + } + const parts = value.split(':'); + if (parts.length !== 2) { + return false; + } + return ResourceLocation.isValidNamespace(parts[0]) + && ResourceLocation.isValidPath(parts[1]); + } + + static parseResourceLocation(value) { + if (!ResourceLocation.isValidResourceLocation(value)) { + return null; + } + const parts = value.split(':'); + return {namespace: parts[0], path: parts[1]}; + } + + static buildResourceLocation(namespace, path) { + return `${namespace}:${path}`; + } + + // e.g. "My Model.bbmodel" -> "my_model" + static sanitizeProfileId(name) { + const base = String(name || '') + .replace(/\.[^.]+$/, '') + .toLowerCase() + .replace(/[^a-z0-9_./-]+/g, '_') + .replace(/_{2,}/g, '_') + .replace(/^[_./-]+|[_./-]+$/g, ''); + return base.length > 0 ? base : 'entity'; + } + } + + module.exports = {ResourceLocation}; + + /***/ + }, + + /***/ 768 + (module) { + + "use strict"; + module.exports = "\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n"; + + /***/ + }, + + /***/ 377 + (module) { + + "use strict"; + module.exports = "# {{id}} - Easy Model Entities pack\n\nGenerated by the **Easy Model Entities Exporter** Blockbench plugin for the **Easy Model Entities**\nmod on **{{mcVersion}}**.\n\n> **Beta:** The exporter is still in beta. The pack format may change in a future version, so you\n> might need to re-export this pack after updating the plugin or the mod.\n\nThis archive contains two ready-to-use packs that belong together:\n\n- `datapack.zip`: the server-side entity profile (gameplay data)\n- `resourcepack.zip`: the model, texture and render profile (visuals)\n\nProfile ID: `{{serverProfileId}}`\n\n## Requirements\n\nYou need the **Easy Model Entities** mod installed for **{{mcVersion}}** (Forge or Fabric).\nWithout the mod these files do nothing. Get the mod from its distribution page (CurseForge /\nModrinth) and install it like any other mod before using this pack.\n\n## Installation\n\nYou do not need to unpack anything — just move the two ZIP files into the right folders.\n\n### 1. Resource Pack (visuals, client side)\n\nMove `resourcepack.zip` into your Minecraft resourcepacks folder:\n\n- Windows: `%appdata%\\.minecraft\\resourcepacks\\`\n- Linux: `~/.minecraft/resourcepacks/`\n- macOS: `~/Library/Application Support/minecraft/resourcepacks/`\n\nThen start Minecraft and enable the pack under **Options > Resource Packs**.\n\n### 2. Data Pack (gameplay, world / server side)\n\nMove `datapack.zip` into your world's `datapacks` folder:\n\n- Single player: `.minecraft/saves//datapacks/`\n- Dedicated server: `/world/datapacks/`\n\nLoad the world (or run `/reload` in game). On a dedicated server the Easy Model Entities mod must be\ninstalled on the server as well.\n\n> Install **both** packs. The data pack defines the entity, the resource pack provides its model and\n> texture.\n"; + + /***/ + }, + + /***/ 896 + (module) { + + "use strict"; + module.exports = require("fs"); + + /***/ + }, + + /***/ 928 + (module) { + + "use strict"; + module.exports = require("path"); + + /***/ + } + + /******/ + }); + /************************************************************************/ + /******/ // The module cache + /******/ + var __webpack_module_cache__ = {}; + /******/ + /******/ // The require function + /******/ + function __webpack_require__(moduleId) { + /******/ // Check if module is in cache + /******/ + var cachedModule = __webpack_module_cache__[moduleId]; + /******/ + if (cachedModule !== undefined) { + /******/ + return cachedModule.exports; + /******/ + } + /******/ // Create a new module (and put it into the cache) + /******/ + var module = __webpack_module_cache__[moduleId] = { + /******/ // no module.id needed + /******/ // no module.loaded needed + /******/ exports: {} + /******/ + }; + /******/ + /******/ // Execute the module function + /******/ + __webpack_modules__[moduleId](module, module.exports, __webpack_require__); + /******/ + /******/ // Return the exports of the module + /******/ + return module.exports; + /******/ + } + + /******/ + /************************************************************************/ + /* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + const { + getDefaults, + deepMerge, + applyTemplate, + DEFAULT_PRESET + } = __webpack_require__(668); + const {ModelDimensions} = __webpack_require__(763); + const {PresetDetector} = __webpack_require__(858); + const {ResourceLocation} = __webpack_require__(20); + const {Validator} = __webpack_require__(229); + const {buildPackBundle, buildModProjectFiles} = __webpack_require__(869); + const {BlockbenchAdapter} = __webpack_require__(833); + const {openExportDialog} = __webpack_require__(924); + const {registerTranslations, t} = __webpack_require__(16); + const actionIconSvg = __webpack_require__(768); + + const ACTION_ID = 'eme_export'; + const SETTING_ID = 'eme_enable_customization'; + const EXPERIMENTAL_SETTING_ID = 'eme_enable_experimental'; + const ACTION_ICON = 'data:image/svg+xml,' + encodeURIComponent(actionIconSvg); + let exportAction; + let projectProperty; + let customizationSetting; + let experimentalSetting; + + function customizationEnabled() { + if (customizationSetting && typeof customizationSetting.value + === 'boolean') { + return customizationSetting.value; + } + return false; + } + + function experimentalEnabled() { + if (experimentalSetting && typeof experimentalSetting.value === 'boolean') { + return experimentalSetting.value; + } + return false; + } + + function resolveDialogState() { + const storedSettings = BlockbenchAdapter.loadSettings(); + const stats = BlockbenchAdapter.getModelStats(); + const bounds = BlockbenchAdapter.getModelBounds(); + + let settings; + let preset; + let blockPreset; + let modelType; + let customize; + let exportType; + + if (storedSettings) { + settings = deepMerge(getDefaults(), storedSettings); + modelType = settings.modelType || 'entity'; + customize = !!storedSettings.customize; + exportType = storedSettings.exportType + || (storedSettings.exportTarget === 'mod_project' ? 'mod_project' + : 'packs'); + if (modelType === 'block_entity') { + preset = 'custom'; + blockPreset = settings.presetType || 'static'; + } else { + preset = settings.presetType || DEFAULT_PRESET; + blockPreset = 'static'; + } + } else { + preset = PresetDetector.detectPresetType(stats, bounds); + blockPreset = 'static'; + modelType = 'entity'; + settings = applyTemplate(preset); + ModelDimensions.applyModelDimensions(settings, + ModelDimensions.deriveDimensions(bounds, settings.host.bodyType)); + customize = false; + exportType = 'packs'; + const projectName = BlockbenchAdapter.getProjectName(); + if (projectName) { + settings.profileId = ResourceLocation.sanitizeProfileId(projectName); + } + } + + const modelDimensions = ModelDimensions.deriveDimensions(bounds, + settings.host.bodyType); + return { + settings, preset, blockPreset, modelType, customize, exportType, + modelDimensions + }; + } + + function formatIssues(issues) { + return issues.map((issue) => `• ${issue.message}`).join('\n'); + } + + function collectContext() { + const stats = BlockbenchAdapter.getModelStats(); + return Object.assign({}, stats, { + textureCount: BlockbenchAdapter.getTextureCount(), + visibleBoundsManual: true + }); + } + + function runExport(settings, target) { + const context = collectContext(); + const result = Validator.validateSettings(settings, context); + + if (!result.valid) { + Blockbench.showMessageBox({ + title: t('eme.dialog.title'), + message: 'Export blocked. Please fix the following issues:\n\n' + + formatIssues(result.errors) + }); + return; + } + + const doExport = () => performExport(settings, target); + + if (result.warnings.length > 0) { + Blockbench.showMessageBox( + { + title: t('eme.dialog.title'), + message: 'The following warnings were found:\n\n' + formatIssues( + result.warnings) + '\n\nExport anyway?', + buttons: ['Export', 'Cancel'], + confirm: 0, + cancel: 1 + }, + (button) => { + if (button === 0) { + doExport(); + } + } + ); + } else { + doExport(); + } + } + + function performExport(settings, target) { + const options = { + modelBytes: BlockbenchAdapter.getModelBytes(), + textureBytes: BlockbenchAdapter.getTextureBytes() + }; + + BlockbenchAdapter.saveSettings(settings); + + if (target === 'mod_project') { + exportToModProject(settings, options); + } else { + exportToZip(settings, options); + } + } + + function exportToZip(settings, options) { + const bundle = buildPackBundle(settings, options); + BlockbenchAdapter.exportPackBundle(bundle, + `${settings.namespace}_${settings.profileId}_eme`) + .then(() => { + Blockbench.showQuickMessage('Easy Model Entities packs exported', 1500); + }) + .catch((error) => { + Blockbench.showMessageBox( + {title: 'Export failed', message: String(error)}); + }); + } + + function exportToModProject(settings, options) { + const rootDir = BlockbenchAdapter.pickDirectory( + 'Select src/main/resources directory'); + if (!rootDir) { + return; + } + + const {files} = buildModProjectFiles(settings, options); + const existing = BlockbenchAdapter.listExistingFiles(rootDir, files); + + const write = () => { + try { + BlockbenchAdapter.writeToDirectory(rootDir, files); + Blockbench.showQuickMessage( + 'Easy Model Entities files written to mod project', 1500); + } catch (error) { + Blockbench.showMessageBox( + {title: 'Export failed', message: String(error)}); + } + }; + + if (existing.length > 0) { + Blockbench.showMessageBox( + { + title: 'Overwrite existing files?', + message: `${existing.length} file(s) already exist and will be overwritten:\n\n` + + existing.join('\n'), + buttons: ['Overwrite', 'Cancel'], + confirm: 0, + cancel: 1 + }, + (button) => { + if (button === 0) { + write(); + } + } + ); + } else { + write(); + } + } + + function openDialog() { + if (!Project) { + Blockbench.showQuickMessage('Open a project first', 1500); + return; + } + const state = resolveDialogState(); + openExportDialog({ + settings: state.settings, + preset: state.preset, + blockPreset: state.blockPreset, + modelType: state.modelType, + customize: state.customize, + exportType: state.exportType, + showCustomization: customizationEnabled(), + experimental: experimentalEnabled(), + modelDimensions: state.modelDimensions, + onExport: runExport + }); + } + + BBPlugin.register('easy_model_entities', { + title: 'Easy Model Entities Exporter', + icon: 'icon.png', + author: 'Markus Bordihn', + description: 'Export Blockbench models for the Easy Model Entities mod (Minecraft: Java Edition) as ready-to-install Data Pack and Resource Pack files.', + tags: ['Minecraft: Java Edition', 'Exporter', 'Entity'], + version: '0.1.0', + min_version: '4.9.0', + variant: 'desktop', + has_changelog: true, + website: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities', + repository: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities', + bug_tracker: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities/issues', + onload() { + registerTranslations(); + + projectProperty = new Property(ModelProject, 'object', + BlockbenchAdapter.PROJECT_PROPERTY, { + label: 'Easy Model Entities Settings', + exposed: false, + default: undefined + }); + + customizationSetting = new Setting(SETTING_ID, { + name: t('eme.setting.enable_customization'), + category: 'export', + type: 'toggle', + value: false + }); + + experimentalSetting = new Setting(EXPERIMENTAL_SETTING_ID, { + name: t('eme.setting.enable_experimental'), + category: 'export', + type: 'toggle', + value: false + }); + + exportAction = new Action(ACTION_ID, { + name: 'Export Easy Model Entities Pack', + description: 'Export the current project as Easy Model Entities Data Pack and Resource Pack.', + icon: ACTION_ICON, + click() { + openDialog(); + } + }); + + MenuBar.addAction(exportAction, 'file.export'); + }, + onunload() { + if (exportAction) { + exportAction.delete(); + } + if (customizationSetting) { + customizationSetting.delete(); + } + if (experimentalSetting) { + experimentalSetting.delete(); + } + if (projectProperty) { + projectProperty.delete(); + } + } + }); + + /******/ +})() +; diff --git a/plugins/easy_model_entities/icon.png b/plugins/easy_model_entities/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7e7bf10a3ea50f305adc8969e8010b9b87a8b375 GIT binary patch literal 5255 zcmV;26nN{2P)_{r~^}Yp5$)nkZ3`Cu*lE03Rs-{rzsLELoZ;x7knp{r+vKD_xu@TbwBV|Npt# zQC^)XZK^9(mMH)J{|_}K{QUeMRUz;8_ezT=02(U(|Ni>@{`vd-1t%y+hbLs9DLQ*5 z^!fQzlPBf!^AtQM01PK?tt?uXDF9(=PmU-6YIF!LCp2><|N8pO=;{J?fdFuN;o>_xQcv;s9lD02C-0L?`O>^=hatICv%~U?t!0@bmrtf4jW~e1!m4 zVrZl)V4^8Nbto}wCIAy1{`K|N?CincR)D!QWTh$+Kq47TAOHOSn9k97x4jWBC(z^M zIGLbDfG0hFCIA8@000o``1{4;HZgUsFX-#+QJP-kQhDMW?S?rK$i?T)f>;pUXr58X+4`Alml% zsO9Ro<>-LJ$RL!V8jqJAhmQe-j>qFyk-O z@AFQvwmYJ!MWClCo~EhIPE3C)R-7pta0Gkp*RhjJbJRZ*$AwY;dhv zoVP||kvFE#F(zy!B-Gc##;lB;oPV5(`@R}{xdE?}M*fkHwGWHCcRQj(Mr_K*#g77%136p__}$P@|+ zxDY{cwC=6dR$E7FtKCz(_uhN&z0Q62y}YcpU%#*IU&Fh(FFC(^&OP_!=7JNC$LI4H zacw>bx9#t6_emdVk+$`FNzeZQaQsHgw-VWaSo8K^5NG560B}aRx7mis*1V&8j$!{j z0ADzW{S?}o2We@FtI>;ITP18<8dJy6aS|AV#x$EBa zu`#V-j{K%?_P?-LS6*JOc<@zIAwp*3T$s>3FFyLzH8vrF?xOW^MGgQ$DazkzjLXpO z9+x2`zG=gwVGZZqv*%T8+T{PheL=_p;OiHU-@^gS#`p{3>JqKX6@=ZXWlvw>300+_v<>?V-7wDg+PSY!Ys^E{}Jd+y4}w>EQq%y*8uB8USNA7&UQ| zk9Xxq@MPrP%13WYy6w$3pRb=j^UZ@WBNaD3aG!j$CE&gN%6qIz1;wNkwe#p!Idm~& z&x>uT%g`9oThhSx(0JD{LY|&tF#w)_zP|s~S+w3b78<>&V%3AMY=(D`bSb>B|7o^_ z5`aWPo<-=lu9x94NWgx6Z=5E>SlXRW!5f9bv^C>*k5|e7FzeCh>sR*AVgUWK`p?jQ zeDLL~a>U1g)C+IkLC*lO>=Yzx?Q7Cg6rWGwD1b59&9#%-vAEs$R*|USK0ZHhyZ*rA z%K&hpe&sA%O?O0BMEhU;@y#ZLB=msAsPN!>7v6M2P89X->qoW4iS*d<9pPQtj127p zdg!c#6yIq$G4;Spvdt@w+XGgtxb@6Kx37w>P<*s@&ijX?ZsGmNdM8$c`f^!KyoFCtp8=UH9UGnbO2 zb$ajki|5WgeK3Xx9!M$uZeyJB1fKvxwq~?;>4#W=G-z$t-7lx6hWJu6VEXuNw>|tY z%!3VJ#fm-Cx9gs}lK}49f9~8v2Tdksp?S(Cv`2T7h@-?lLzjN^$dfH=aP>KG_kp!0 z%ZfVp;b)&sx&h2%tFz$$tjnihfqZ^iZr1I*w0hT2uMFSsl;fdJ4KPalM3Z~nj` zYk_I`rcdIQlwUhFHPu_3hNP5b@!Rtj44V-i`ilOBV0V$5$i>agZD8J&momkZFBapL z6@Wxto@!V}MjWEAT)V?)gd$-H`06`0<`=EFY{O-@PM>!bxw(6QeGGVx0L$Q{GJkg^m5_z=Q^ZEv{jqLp(S0=&H37`eFkfd}-fz+7P= zgb0KIc-S?k+YaZ`%@D&n0;ty0FAb_`GfeH;62t(0`EV8;oDD0cuL{U2C}-+p~^0Uc2_D? zo6o=7umN&1t02>P23CMO2T-yA^wjZZk01a2i+jKWaERi`WfHs!hzF6^62L^UP{>ZM zg!1GiF2CNe?$Dm;rC9}F*x8vpum>(60+_duNqY%j!rLr?=wJZigIHto#K^}9pt^|z z(6tf8tX=op!%GWhgJBNfZUOKi9*{ZQd=r?p-M$kKghJeB3bNdalSbv}zNO9}Fiwli z0T>6lxXdR2tIdENKoY_Ng6sg0#3z?}a{$@{4@VhGg$c)$%xEEYQg@Iy2VAa^S}y#5I4!Rm@Hxdb6}z2 zYg-RbepstHateI7hW0>xOb4UZxsyOeVf#^w-M|9?Sa0QR0c0Jp9&{1FJ0%p9?Z$VH z9^mmd({r%b-yZ|iEC4GT0P`IIY`5P+^Ez0xmh?VZO}|`z6l#>XxIGB~w8?pDvfvc) zQStHf=DWc{3*{ymUmXC*O11$sl9%4n%H!QsjOQR5$3&oD06r>#q9a@6@+SbU$+(rM zVXA4-1fL%h10kv*z+38sLh!&D0K@}kk`ol+Sst`t4nDFjJ@x?bfCVhSmjFhYJ@;z> zxM>cAhym!%()OLzt_3V$o+Q&K3Y3#O-G0Ku=#x3r6`A4d}yJgaL~c*nWo#w(mV-UwE(?aHS)F&6n;( z?QXgyUe09C{gJg*2vO86>tP)LV77C%Psie(oN0kb=F>dbC8ro0E&2>LI7pRKVIeIswRN>QQ(0s!0y~cGC9`P z6av;-UZ(phN1B}S*l(hh&CW@)X@PZI<>Cd3ZBT|FgGAxUI@JevT8GFU06G+z{=|+F z9;($-bWKm}o+zqGLo0w6dbe_f7ppU4dtx^1BX)s3H-8~|*cLPOnc=|}GGIq8cK z(-(AjWZ96fNi0t5@do4G-ZN(kRQ|Bm{Iak_%IpNy#s@zA_(v169Bx;%J#dFT!1|z)%9AGE zZOG81pOgvbbo&mSj*Eovw3ONNv{UU6A}~KYGaw*5AztOL^SL}Bp)+|PFz}KTI7fQ! z=T{IS_8bCA4!3HKYVg;5p75~~CST-KRF|PWy@)-eLp@1LF~Hk-@9hby@~DK)&dvk? z1P+u9_=Woo_(VrXKkzE9F!pK6Glf*|j2qLeTNWWh6y7?v`1;}_ve;clZEtrP9pA#9 zr46trI(tl~Dp&cW4CdtI3?_tzb|#mV4P4k47IxM74pn*i1FvpoedYj2-t&Y(YZ%EV zTP#q|y)+;uf?7M$XPBB8!YjG^;jpmt+u~I!K?hg|Ey?LfNJuGDhKmBu1K_HLfZKJ^ z(a#*T10;@Ww39_?6jy8)jR1f=3##3*Ta&SFIPaP*FLcf;FZcK9Tyn`JOO`Cj$+;wX zu%lzKI?H)xL&H^|xpV91etY$%2kiiB(~Z3~cTkWqEMQA-A^?aUdkhWl&}wV9FTc`1 zUa>l55bVEW&xF@OZ0%P&q@tiIumGiR=v z;q8noReUr%G&7*8t@lo{Bfe`Yy>wS|Y{}?Iq8Wf^1MX62IgCF!a3EOiZU$rnVD;*Y zQ_j9IJ-0p6*%rWVwDrBcih@QYQrK=an-Q6vi0fd4F z7e8`wj@p|D04fXsSpud?X@FD;5#PdtzLT{qfG00*Vk+&NGCwyD0#KU)It+MZ@T~Jp zGa%Qh7U74!t#k{I(V`MMHNgWWfc)5eCbi~>;CzJuTr52R4Z6={0AXPO0GR;?dLkYfOr zy$2vE07^MP3;wB}%9pc%X1YfT9(Z{D9l*u|)x!10AD*SeZQB3AWs zfGyQxb)KGk>Vz!5(zu10Th>C0qPt8a`;~O z%&&N6A!3gPSpX=)6g#3BGv1rVJJEz!ZP9`d345>c6as+BH2$R00^mmjmiW+s)q`cO zu72AT#!xpu@5x3#OW1R-%mNpp}Sf>vQf{uU}CFE1kEnv-W;{rr?b zffxlVmC2wN5`1V6QU$$nBHm=&3yDAqR_$T~RMXy@5F=|ys#U>{9#F8TbG=w}v zEjx5w>(=$_)7Qg~y7aofzSgaMb#+FAp|92Ox_>;#pHoznLZA>pZ`AuRdV7GwKUn~b zkmrf;1OG$t;88fUJf+Z(B1Ctwx(P`o626rCZonO>nE@P?yF%W;gO&N)&!6gV0QMlq z{V~iNJwfDZ9|87?@BVW@%s&LM4*eAb8{zt~VcbK4@^`WW@c&(a(;qSPw { + let depth = 1; + let parent = group.parent; + while (parent && parent !== 'root' && typeof parent === 'object') { + depth += 1; + parent = parent.parent; + } + if (depth > maxDepth) { + maxDepth = depth; + } + }); + + return { + hasModel: cubes.length > 0 || groups.length > 0, + hasTexture: !!texture, + cubeCount: cubes.length, + boneCount: groups.length, + hierarchyDepth: maxDepth, + boneNames: groups.map((group) => group.name), + textureWidth: texture ? texture.width : undefined, + textureHeight: texture ? texture.height : undefined + }; + } + + static getModelBytes() { + return Codecs.project.compile({raw: false}); + } + + static getModelBounds() { + const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; + if (cubes.length === 0) { + return null; + } + const bounds = { + minX: Infinity, + minY: Infinity, + minZ: Infinity, + maxX: -Infinity, + maxY: -Infinity, + maxZ: -Infinity + }; + cubes.forEach((cube) => { + const from = cube.from || [0, 0, 0]; + const to = cube.to || [0, 0, 0]; + bounds.minX = Math.min(bounds.minX, from[0], to[0]); + bounds.minY = Math.min(bounds.minY, from[1], to[1]); + bounds.minZ = Math.min(bounds.minZ, from[2], to[2]); + bounds.maxX = Math.max(bounds.maxX, from[0], to[0]); + bounds.maxY = Math.max(bounds.maxY, from[1], to[1]); + bounds.maxZ = Math.max(bounds.maxZ, from[2], to[2]); + }); + return bounds; + } + + static getProjectName() { + if (typeof Project === 'undefined' || !Project) { + return ''; + } + if (Project.geometry_name) { + return Project.geometry_name; + } + if (Project.name) { + return Project.name; + } + if (typeof Project.getDisplayName === 'function') { + return Project.getDisplayName(false); + } + return ''; + } + + static getTextureBytes() { + const texture = BlockbenchAdapter.getActiveTexture(); + if (!texture) { + return new Uint8Array(0); + } + return BlockbenchAdapter.#base64ToBytes(texture.getBase64()); + } + + static loadSettings() { + if (typeof Project === 'undefined' || !Project) { + return null; + } + const stored = Project[BlockbenchAdapter.PROJECT_PROPERTY]; + if (stored && typeof stored === 'object') { + return structuredClone(stored); + } + return null; + } + + static saveSettings(settings) { + if (typeof Project !== 'undefined' && Project) { + Project[BlockbenchAdapter.PROJECT_PROPERTY] = structuredClone( + settings); + if (typeof Blockbench !== 'undefined' && Blockbench.dispatchEvent) { + Blockbench.dispatchEvent('eme_settings_saved', {settings}); + } + } + } + + static #zipToUint8(files) { + const zip = new JSZip(); + files.forEach((file) => { + zip.file(file.path, file.content); + }); + return zip.generateAsync({type: 'uint8array'}); + } + + // Wraps a ready-to-drop datapack.zip and resourcepack.zip plus the README + // into a single outer ZIP, so the user can move the inner ZIPs straight into + // their datapacks/ and resourcepacks/ folders without unpacking. + static exportPackBundle(bundle, name) { + return Promise.all([ + BlockbenchAdapter.#zipToUint8(bundle.datapack), + BlockbenchAdapter.#zipToUint8(bundle.resourcepack) + ]).then(([datapackZip, resourcepackZip]) => { + const outer = new JSZip(); + outer.file(bundle.readme.path, bundle.readme.content); + outer.file('datapack.zip', datapackZip); + outer.file('resourcepack.zip', resourcepackZip); + return outer.generateAsync({type: 'blob'}); + }).then((content) => { + return new Promise((resolve) => { + Blockbench.export( + { + type: 'Zip Archive', + extensions: ['zip'], + name: name, + content: content, + savetype: 'zip' + }, + (path) => resolve(path) + ); + }); + }); + } + + static listExistingFiles(rootDir, files) { + const fs = require('fs'); + const path = require('path'); + return files + .map((file) => path.join(rootDir, file.path)) + .filter((fullPath) => fs.existsSync(fullPath)); + } + + static writeToDirectory(rootDir, files) { + const fs = require('fs'); + const path = require('path'); + files.forEach((file) => { + const fullPath = path.join(rootDir, file.path); + fs.mkdirSync(path.dirname(fullPath), {recursive: true}); + const data = + file.binary && file.content instanceof Uint8Array ? Buffer.from( + file.content) : file.content; + fs.writeFileSync(fullPath, data); + }); + } + + static pickDirectory(title) { + return Blockbench.pickDirectory( + {title: title, resource_id: 'eme_mod_project'}); + } +} + +module.exports = {BlockbenchAdapter}; diff --git a/plugins/easy_model_entities/src/LICENSE.MD b/plugins/easy_model_entities/src/LICENSE.MD new file mode 100644 index 00000000..735887cf --- /dev/null +++ b/plugins/easy_model_entities/src/LICENSE.MD @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Markus Bordihn + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/easy_model_entities/src/builders/exporter.js b/plugins/easy_model_entities/src/builders/exporter.js new file mode 100644 index 00000000..ccc70d8c --- /dev/null +++ b/plugins/easy_model_entities/src/builders/exporter.js @@ -0,0 +1,125 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {buildServerProfile} = require('./serverProfile'); +const {buildRenderProfile} = require('./renderProfile'); +const {buildDataPackMcmeta, buildResourcePackMcmeta} = require('./packMeta'); +const {buildReadme} = require('./readme'); + +function toJson(value) { + return JSON.stringify(value, null, 2) + '\n'; +} + +function buildProfiles(settings) { + return { + serverProfile: buildServerProfile(settings), + renderProfile: buildRenderProfile(settings) + }; +} + +function dataPaths(settings) { + const ns = settings.namespace; + const id = settings.profileId; + // Server profiles live in an entity/ or block_entity/ subfolder. Models and + // textures stay at the mod's auto-resolved default location so the render + // profile never has to spell out a model/texture path. + const modelType = settings.modelType || 'entity'; + return { + profile: `data/${ns}/easy_model_entities/profiles/${modelType}/${id}.json`, + renderProfile: `assets/${ns}/easy_model_entities/render_profiles/${id}.json`, + model: `assets/${ns}/easy_model_entities/models/${id}.bbmodel`, + texture: `assets/${ns}/textures/entity/${id}.png` + }; +} + +function datapackFiles(settings, serverProfile) { + return [ + { + path: 'pack.mcmeta', content: toJson(buildDataPackMcmeta(settings)), + binary: false + }, + { + path: dataPaths(settings).profile, content: toJson(serverProfile), + binary: false + } + ]; +} + +function resourcepackFiles(settings, renderProfile, options) { + const paths = dataPaths(settings); + return [ + { + path: 'pack.mcmeta', content: toJson(buildResourcePackMcmeta(settings)), + binary: false + }, + {path: paths.renderProfile, content: toJson(renderProfile), binary: false}, + {path: paths.model, content: options.modelBytes, binary: true}, + {path: paths.texture, content: options.textureBytes, binary: true} + ]; +} + +// Bundle to wrap into the outer export ZIP: a ready-to-drop datapack.zip and +// resourcepack.zip plus an install README. +function buildPackBundle(settings, options) { + const opts = options || {}; + const {serverProfile, renderProfile} = buildProfiles(settings); + return { + readme: {path: 'README.md', content: buildReadme(settings), binary: false}, + datapack: datapackFiles(settings, serverProfile), + resourcepack: resourcepackFiles(settings, renderProfile, opts), + serverProfile, + renderProfile + }; +} + +function buildModProjectFiles(settings, options) { + const opts = options || {}; + const renderProfile = buildRenderProfile(settings); + const paths = dataPaths(settings); + + const files = []; + let serverProfile = null; + // Model-only export (mod integration, e.g. the Mimic example): the mod ships + // its own entity classes, so no server profile / data pack is written. + if (!settings.modelOnly) { + serverProfile = buildServerProfile(settings); + files.push({ + path: paths.profile, content: toJson(serverProfile), + binary: false + }); + } + + files.push( + { + path: paths.renderProfile, content: toJson(renderProfile), + binary: false + }, + {path: paths.model, content: opts.modelBytes, binary: true}, + {path: paths.texture, content: opts.textureBytes, binary: true}); + + return {files, serverProfile, renderProfile}; +} + +module.exports = { + buildPackBundle, + buildModProjectFiles, + dataPaths, + datapackFiles, + resourcepackFiles +}; diff --git a/plugins/easy_model_entities/src/builders/packMeta.js b/plugins/easy_model_entities/src/builders/packMeta.js new file mode 100644 index 00000000..f834c38d --- /dev/null +++ b/plugins/easy_model_entities/src/builders/packMeta.js @@ -0,0 +1,46 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {getPackFormats} = require('../model/versionMatrix'); + +function buildMcmeta(settings, packFormat, description) { + const formats = getPackFormats(settings.targetVersion); + if (!formats) { + throw new Error( + `Unsupported or disabled target version: ${settings.targetVersion}`); + } + return { + pack: { + pack_format: formats[packFormat], + description: description + } + }; +} + +function buildDataPackMcmeta(settings) { + return buildMcmeta(settings, 'dataFormat', + 'Easy Model Entities server profiles'); +} + +function buildResourcePackMcmeta(settings) { + return buildMcmeta(settings, 'resourceFormat', + 'Easy Model Entities render assets'); +} + +module.exports = {buildDataPackMcmeta, buildResourcePackMcmeta}; diff --git a/plugins/easy_model_entities/src/builders/profileDiff.js b/plugins/easy_model_entities/src/builders/profileDiff.js new file mode 100644 index 00000000..ebd09043 --- /dev/null +++ b/plugins/easy_model_entities/src/builders/profileDiff.js @@ -0,0 +1,53 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Helpers to emit minimal (diff-based) profiles: only values that deviate from +// the defaults the mod derives from preset_type are written. + +function valuesDiffer(a, b) { + if (Array.isArray(a) || Array.isArray(b)) { + return JSON.stringify(a) !== JSON.stringify(b); + } + return a !== b; +} + +// Returns a new object containing only the keys whose value differs from the +// matching key in defaults. +function diffFlat(values, defaults) { + const result = {}; + Object.keys(values).forEach((key) => { + if (valuesDiffer(values[key], defaults[key])) { + result[key] = values[key]; + } + }); + return result; +} + +function isEmpty(object) { + return !object || Object.keys(object).length === 0; +} + +// Assigns section to target under key only when it has at least one entry. +function assignIfPresent(target, key, section) { + if (!isEmpty(section)) { + target[key] = section; + } +} + +module.exports = {valuesDiffer, diffFlat, isEmpty, assignIfPresent}; diff --git a/plugins/easy_model_entities/src/builders/readme.js b/plugins/easy_model_entities/src/builders/readme.js new file mode 100644 index 00000000..90606fea --- /dev/null +++ b/plugins/easy_model_entities/src/builders/readme.js @@ -0,0 +1,37 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {ResourceLocation} = require('../utils/ResourceLocation'); +const TEMPLATE = require('../resources/readme.md'); + +function buildReadme(settings) { + const id = ResourceLocation.buildResourceLocation(settings.namespace, + settings.profileId); + const modelType = settings.modelType || 'entity'; + const serverProfileId = ResourceLocation.buildResourceLocation( + settings.namespace, `${modelType}/${settings.profileId}`); + return TEMPLATE + .replaceAll('{{id}}', id) + .replaceAll('{{serverProfileId}}', serverProfileId) + .replaceAll('{{mcVersion}}', + `Minecraft: Java Edition ${settings.targetVersion}`) + .replaceAll('{{folderName}}', `${settings.namespace}_eme`); +} + +module.exports = {buildReadme}; diff --git a/plugins/easy_model_entities/src/builders/renderProfile.js b/plugins/easy_model_entities/src/builders/renderProfile.js new file mode 100644 index 00000000..31fda4c7 --- /dev/null +++ b/plugins/easy_model_entities/src/builders/renderProfile.js @@ -0,0 +1,111 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const { + bodyType, + animationMode, + presetRenderBounds, + isCustom, + MODEL_TYPE_BLOCK_ENTITY +} = require('../model/presetTypes'); +const {diffFlat, assignIfPresent} = require('./profileDiff'); + +// Block entities render as a static cuboid block, so they always use the +// "static" render preset regardless of their server-side block entity preset. +function renderPresetType(settings) { + return settings.modelType === MODEL_TYPE_BLOCK_ENTITY + ? 'static' : settings.presetType; +} + +function buildModelReference(settings) { + return `${settings.namespace}:easy_model_entities/models/${settings.profileId}`; +} + +function buildTextureReference(settings) { + return `${settings.namespace}:textures/entity/${settings.profileId}.png`; +} + +function buildRendering(settings) { + const [boundsWidth, boundsHeight, boundsOffsetY, shadowRadius] = + presetRenderBounds(renderPresetType(settings)); + return diffFlat({ + scale: settings.rendering.scale, + shadow_radius: settings.rendering.shadowRadius, + visible_bounds_width: settings.rendering.visibleBoundsWidth, + visible_bounds_height: settings.rendering.visibleBoundsHeight, + visible_bounds_offset: settings.rendering.visibleBoundsOffset.slice() + }, { + scale: 1, + shadow_radius: shadowRadius, + visible_bounds_width: boundsWidth, + visible_bounds_height: boundsHeight, + visible_bounds_offset: [0, boundsOffsetY, 0] + }); +} + +function buildAnimation(settings) { + const defaultMode = animationMode(renderPresetType(settings)); + const animation = diffFlat({ + mode: settings.animation.mode, + swing_speed: settings.animation.swingSpeed, + walk_speed_multiplier: settings.animation.walkSpeedMultiplier + }, { + mode: defaultMode, + swing_speed: 1, + walk_speed_multiplier: 1 + }); + // Animation timing is meaningless when animation is disabled. + if (settings.animation.mode === 'none') { + delete animation.swing_speed; + delete animation.walk_speed_multiplier; + } + return animation; +} + +function buildRenderProfile(settings) { + const renderPreset = renderPresetType(settings); + const custom = settings.modelType !== MODEL_TYPE_BLOCK_ENTITY + && isCustom(settings.presetType); + const profile = { + schema_version: settings.schemaVersion, + preset_type: renderPreset + }; + if (settings.version) { + profile.version = settings.version; + } + + if (custom || settings.host.bodyType !== bodyType(renderPreset)) { + profile.body_type = settings.host.bodyType; + } + + // model and texture follow the mod's conventional default path + // (namespace:easy_model_entities/models/id and namespace:textures/entity/id.png), + // so they are omitted and resolved automatically by the mod. + + assignIfPresent(profile, 'rendering', buildRendering(settings)); + assignIfPresent(profile, 'animation', buildAnimation(settings)); + + return profile; +} + +module.exports = { + buildRenderProfile, + buildModelReference, + buildTextureReference +}; diff --git a/plugins/easy_model_entities/src/builders/serverProfile.js b/plugins/easy_model_entities/src/builders/serverProfile.js new file mode 100644 index 00000000..4b84a804 --- /dev/null +++ b/plugins/easy_model_entities/src/builders/serverProfile.js @@ -0,0 +1,146 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const { + presetDefaults, + presetDimensions, + movementDefaults, + behaviorModeFor, + isCustom, + MODEL_TYPE_BLOCK_ENTITY, + MODEL_TYPE_ENTITY +} = require('../model/presetTypes'); +const {diffFlat, assignIfPresent} = require('./profileDiff'); + +const DEFAULT_MAX_HEALTH = 10; +const DEFAULT_FOLLOW_RANGE = 16; + +function effectiveDefaults(settings) { + const preset = settings.presetType; + const move = settings.host.movementType; + const movement = movementDefaults(preset, move); + const mode = behaviorModeFor(preset, move); + const dimensions = settings.modelType === MODEL_TYPE_BLOCK_ENTITY + ? presetDefaults(preset, MODEL_TYPE_BLOCK_ENTITY).dimensions + : presetDimensions(preset); + return { + movement: { + speed: movement.speed, + step_height: movement.stepHeight, + gravity: movement.gravity + }, + behavior: { + mode: mode, + look_at_players: mode === 'idle_only' || mode === 'ambient', + random_stroll: move === 'ground' && mode === 'ambient' + }, + attributes: { + max_health: DEFAULT_MAX_HEALTH, + movement_speed: settings.movement.speed, + follow_range: DEFAULT_FOLLOW_RANGE + }, + dimensions: dimensions + }; +} + +// Entity host settings. The mod derives type/movement_type/body_type from the +// preset, so only the "custom" preset needs to spell them out; non-custom +// presets emit nothing here. +function buildEntity(settings, custom) { + if (!custom) { + return {}; + } + const host = settings.host; + return { + type: host.entityType, + movement_type: host.movementType, + body_type: host.bodyType + }; +} + +function buildDimensions(settings, defaults, custom) { + const values = { + width: settings.dimensions.width, + height: settings.dimensions.height, + eye_height: settings.dimensions.eyeHeight + }; + if (custom) { + return values; + } + return diffFlat(values, { + width: defaults.dimensions.width, + height: defaults.dimensions.height, + eye_height: defaults.dimensions.eyeHeight + }); +} + +function buildServerProfile(settings) { + const modelType = settings.modelType || MODEL_TYPE_ENTITY; + const blockEntity = modelType === MODEL_TYPE_BLOCK_ENTITY; + const custom = !blockEntity && isCustom(settings.presetType); + const defaults = effectiveDefaults(settings); + + const profile = { + schema_version: settings.schemaVersion, + model_type: modelType, + preset_type: settings.presetType + }; + if (settings.version) { + profile.version = settings.version; + } + + // The server profile lives in a "/" subfolder, so its derived + // id differs from the flat render profile id. The render profile must be named + // explicitly or the mod would look for "/" instead. + profile.client = { + render_profile: `${settings.namespace}:${settings.profileId}` + }; + + if (!blockEntity) { + assignIfPresent(profile, 'entity', buildEntity(settings, custom)); + } + + assignIfPresent(profile, 'dimensions', + buildDimensions(settings, defaults, custom)); + + // Block entities ignore movement, behavior and attributes. + if (!blockEntity) { + assignIfPresent(profile, 'movement', diffFlat({ + speed: settings.movement.speed, + step_height: settings.movement.stepHeight, + gravity: settings.movement.gravity + }, defaults.movement)); + + assignIfPresent(profile, 'behavior', diffFlat({ + mode: settings.behavior.mode, + look_at_players: settings.behavior.lookAtPlayers, + random_stroll: settings.behavior.randomStroll + }, defaults.behavior)); + + assignIfPresent(profile, 'attributes', diffFlat({ + max_health: settings.attributes.maxHealth, + movement_speed: settings.attributes.movementSpeed, + follow_range: settings.attributes.followRange + }, defaults.attributes)); + } + + return profile; +} + +module.exports = {buildServerProfile}; diff --git a/plugins/easy_model_entities/src/i18n/translations.js b/plugins/easy_model_entities/src/i18n/translations.js new file mode 100644 index 00000000..87e506ab --- /dev/null +++ b/plugins/easy_model_entities/src/i18n/translations.js @@ -0,0 +1,219 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Descriptive, human-readable labels so users see e.g. "Quadruped (4 legs, +// ground, standing)" instead of the cryptic "quadruped_still". Registered with +// Blockbench's translation system; falls back to English outside Blockbench. + +const EN = { + 'eme.dialog.title': 'Easy Model Entities Export', + 'eme.field.preset': 'Preset', + 'eme.field.namespace': 'Namespace (mod id)', + 'eme.field.profileId': 'Profile ID', + 'eme.field.version': 'Version (optional)', + 'eme.field.targetVersion': 'Minecraft Version', + 'eme.field.exportType': 'Export Type', + 'eme.field.modelType': 'Type', + 'eme.field.customize': 'Customize settings', + 'eme.field.hostEntityType': 'Host Entity Type', + 'eme.field.movementType': 'Movement Type', + 'eme.field.bodyType': 'Body Type', + 'eme.field.width': 'Width', + 'eme.field.height': 'Height', + 'eme.field.eyeHeight': 'Eye Height', + 'eme.field.speed': 'Speed', + 'eme.field.stepHeight': 'Step Height', + 'eme.field.gravity': 'Gravity', + 'eme.field.behaviorMode': 'Behavior Mode', + 'eme.field.maxHealth': 'Max Health', + 'eme.field.movementSpeed': 'Movement Speed', + 'eme.field.followRange': 'Follow Range', + 'eme.field.scale': 'Scale', + 'eme.field.shadowRadius': 'Shadow Radius', + 'eme.field.visibleBoundsWidth': 'Visible Bounds Width', + 'eme.field.visibleBoundsHeight': 'Visible Bounds Height', + 'eme.field.visibleBoundsOffset': 'Visible Bounds Offset', + 'eme.field.animationMode': 'Animation Mode', + 'eme.field.swingSpeed': 'Swing Speed', + 'eme.field.walkSpeedMultiplier': 'Walk Speed Multiplier', + 'eme.section.host': 'Host', + 'eme.section.dimensions': 'Dimensions', + 'eme.section.movement': 'Movement', + 'eme.section.behavior': 'Behavior', + 'eme.section.attributes': 'Attributes', + 'eme.section.rendering': 'Rendering', + 'eme.section.animation': 'Animation', + 'eme.setting.enable_customization': 'Show advanced customization (Easy Model Entities)', + 'eme.setting.enable_experimental': 'Show experimental presets (Easy Model Entities)', + 'eme.preset.custom': 'Custom (manual settings)', + 'eme.preset.static': 'Static (no animation, no movement)', + 'eme.preset.statue': 'Statue (display, no movement)', + 'eme.preset.ticking': 'Ticking (server + client tick)', + 'eme.preset.animated': 'Animated (continuous animation)', + 'eme.preset.animated_randomly': 'Animated randomly (random idle bursts)', + 'eme.preset.humanoid_still': 'Humanoid (2 legs, standing)', + 'eme.preset.humanoid_wandering': 'Humanoid (2 legs, wandering)', + 'eme.preset.quadruped_still': 'Quadruped (4 legs, ground, standing)', + 'eme.preset.quadruped_wandering': 'Quadruped (4 legs, ground, wandering)', + 'eme.preset.aquatic_still': 'Aquatic (fish, still)', + 'eme.preset.aquatic_swimming': 'Aquatic (fish, swimming)', + 'eme.preset.winged_still': 'Winged (bird, perched)', + 'eme.preset.winged_wandering': 'Winged (bird, flying)', + 'eme.preset.winged_humanoid_still': 'Winged humanoid (standing)', + 'eme.preset.winged_humanoid_wandering': 'Winged humanoid (wandering)', + 'eme.preset.arthropod_still': 'Arthropod (insect/spider, still)', + 'eme.preset.arthropod_wandering': 'Arthropod (insect/spider, wandering)', + 'eme.preset.cuboid_still': 'Cuboid (block shape, still)', + 'eme.preset.cuboid_hopping': 'Cuboid (block shape, hopping)', + 'eme.preset.floating_still': 'Floating (hovering, still)', + 'eme.body.static': 'Static', + 'eme.body.biped': 'Biped (2 legs)', + 'eme.body.quadruped': 'Quadruped (4 legs)', + 'eme.body.aquatic': 'Aquatic', + 'eme.body.winged': 'Winged', + 'eme.body.winged_humanoid': 'Winged humanoid', + 'eme.body.arthropod': 'Arthropod', + 'eme.body.cuboid': 'Cuboid', + 'eme.body.floating': 'Floating', + 'eme.movement.ground': 'Ground', + 'eme.movement.static': 'Static', + 'eme.behavior.idle_only': 'Idle only', + 'eme.behavior.ambient': 'Ambient (wanders)', + 'eme.behavior.static': 'Static', + 'eme.behavior.external_owner': 'External owner', + 'eme.animation.automatic': 'Automatic', + 'eme.animation.random_idle': 'Random idle', + 'eme.animation.none': 'None', + 'eme.entity.ground_entity': 'Ground entity', + 'eme.entity.static_entity': 'Static entity', + 'eme.modelType.entity': 'Entity', + 'eme.modelType.block_entity': 'Block Entity', + 'eme.exportType.packs': 'Standalone: Data Pack + Resource Pack (ZIP)', + 'eme.exportType.mod_project': 'Standalone: write into mod project', + 'eme.exportType.model_only': 'Model only: mod integration (no data pack)' +}; + +const DE = { + 'eme.dialog.title': 'Easy Model Entities Export', + 'eme.field.preset': 'Vorlage', + 'eme.field.namespace': 'Namespace (Mod-ID)', + 'eme.field.profileId': 'Profil-ID', + 'eme.field.version': 'Version (optional)', + 'eme.field.targetVersion': 'Minecraft-Version', + 'eme.field.exportType': 'Export-Typ', + 'eme.field.modelType': 'Typ', + 'eme.field.customize': 'Einstellungen anpassen', + 'eme.field.hostEntityType': 'Host-Entität', + 'eme.field.movementType': 'Bewegungsart', + 'eme.field.bodyType': 'Körpertyp', + 'eme.field.width': 'Breite', + 'eme.field.height': 'Höhe', + 'eme.field.eyeHeight': 'Augenhöhe', + 'eme.field.speed': 'Geschwindigkeit', + 'eme.field.stepHeight': 'Schritthöhe', + 'eme.field.gravity': 'Schwerkraft', + 'eme.field.behaviorMode': 'Verhaltensmodus', + 'eme.field.maxHealth': 'Maximale Lebenspunkte', + 'eme.field.movementSpeed': 'Bewegungsgeschwindigkeit', + 'eme.field.followRange': 'Folgereichweite', + 'eme.field.scale': 'Skalierung', + 'eme.field.shadowRadius': 'Schattenradius', + 'eme.field.visibleBoundsWidth': 'Sichtbarkeitsbox-Breite', + 'eme.field.visibleBoundsHeight': 'Sichtbarkeitsbox-Höhe', + 'eme.field.visibleBoundsOffset': 'Sichtbarkeitsbox-Versatz', + 'eme.field.animationMode': 'Animationsmodus', + 'eme.field.swingSpeed': 'Schwunggeschwindigkeit', + 'eme.field.walkSpeedMultiplier': 'Geh-Geschwindigkeitsfaktor', + 'eme.section.host': 'Host', + 'eme.section.dimensions': 'Abmessungen', + 'eme.section.movement': 'Bewegung', + 'eme.section.behavior': 'Verhalten', + 'eme.section.attributes': 'Attribute', + 'eme.section.rendering': 'Darstellung', + 'eme.section.animation': 'Animation', + 'eme.setting.enable_customization': 'Erweiterte Anpassung anzeigen (Easy Model Entities)', + 'eme.setting.enable_experimental': 'Experimentelle Presets anzeigen (Easy Model Entities)', + 'eme.preset.custom': 'Benutzerdefiniert (manuelle Einstellungen)', + 'eme.preset.static': 'Statisch (keine Animation, keine Bewegung)', + 'eme.preset.statue': 'Statue (Anzeige, keine Bewegung)', + 'eme.preset.ticking': 'Tickend (Server- + Client-Tick)', + 'eme.preset.animated': 'Animiert (durchgehende Animation)', + 'eme.preset.animated_randomly': 'Zufällig animiert (zufällige Leerlauf-Schübe)', + 'eme.preset.humanoid_still': 'Humanoid (2 Beine, stehend)', + 'eme.preset.humanoid_wandering': 'Humanoid (2 Beine, umherlaufend)', + 'eme.preset.quadruped_still': 'Vierbeiner (4 Beine, Boden, stehend)', + 'eme.preset.quadruped_wandering': 'Vierbeiner (4 Beine, Boden, umherlaufend)', + 'eme.preset.aquatic_still': 'Wassertier (Fisch, ruhend)', + 'eme.preset.aquatic_swimming': 'Wassertier (Fisch, schwimmend)', + 'eme.preset.winged_still': 'Geflügelt (Vogel, sitzend)', + 'eme.preset.winged_wandering': 'Geflügelt (Vogel, fliegend)', + 'eme.preset.winged_humanoid_still': 'Geflügelter Humanoid (stehend)', + 'eme.preset.winged_humanoid_wandering': 'Geflügelter Humanoid (umherlaufend)', + 'eme.preset.arthropod_still': 'Gliederfüßer (Insekt/Spinne, ruhend)', + 'eme.preset.arthropod_wandering': 'Gliederfüßer (Insekt/Spinne, umherlaufend)', + 'eme.preset.cuboid_still': 'Quaderförmig (blockartig, ruhend)', + 'eme.preset.cuboid_hopping': 'Quaderförmig (blockartig, hüpfend)', + 'eme.preset.floating_still': 'Schwebend (ruhend)', + 'eme.body.static': 'Statisch', + 'eme.body.biped': 'Zweibeiner (2 Beine)', + 'eme.body.quadruped': 'Vierbeiner (4 Beine)', + 'eme.body.aquatic': 'Wassertier', + 'eme.body.winged': 'Geflügelt', + 'eme.body.winged_humanoid': 'Geflügelter Humanoid', + 'eme.body.arthropod': 'Gliederfüßer', + 'eme.body.cuboid': 'Quaderförmig', + 'eme.body.floating': 'Schwebend', + 'eme.movement.ground': 'Boden', + 'eme.movement.static': 'Statisch', + 'eme.behavior.idle_only': 'Nur Leerlauf', + 'eme.behavior.ambient': 'Umgebung (läuft umher)', + 'eme.behavior.static': 'Statisch', + 'eme.behavior.external_owner': 'Externer Besitzer', + 'eme.animation.automatic': 'Automatisch', + 'eme.animation.random_idle': 'Zufälliger Leerlauf', + 'eme.animation.none': 'Keine', + 'eme.entity.ground_entity': 'Boden-Entität', + 'eme.entity.static_entity': 'Statische Entität', + 'eme.modelType.entity': 'Entität', + 'eme.modelType.block_entity': 'Block-Entität', + 'eme.exportType.packs': 'Standalone: Data Pack + Resource Pack (ZIP)', + 'eme.exportType.mod_project': 'Standalone: in Mod-Projekt schreiben', + 'eme.exportType.model_only': 'Nur Modell: Mod-Integration (kein Data Pack)' +}; + +function registerTranslations() { + if (typeof Language !== 'undefined' && typeof Language.addTranslations + === 'function') { + Language.addTranslations('en', EN); + Language.addTranslations('de', DE); + } +} + +// Resolves a key via Blockbench's tl() when available, otherwise English. +function t(key) { + if (typeof tl === 'function') { + const translated = tl(key); + if (translated && translated !== key) { + return translated; + } + } + return EN[key] || key; +} + +module.exports = {EN, DE, registerTranslations, t}; diff --git a/plugins/easy_model_entities/src/index.js b/plugins/easy_model_entities/src/index.js new file mode 100644 index 00000000..f178a156 --- /dev/null +++ b/plugins/easy_model_entities/src/index.js @@ -0,0 +1,307 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const { + getDefaults, + deepMerge, + applyTemplate, + DEFAULT_PRESET +} = require('./model/templates'); +const {ModelDimensions} = require('./model/ModelDimensions'); +const {PresetDetector} = require('./model/PresetDetector'); +const {ResourceLocation} = require('./utils/ResourceLocation'); +const {Validator} = require('./model/Validator'); +const {buildPackBundle, buildModProjectFiles} = require('./builders/exporter'); +const {BlockbenchAdapter} = require('./BlockbenchAdapter'); +const {openExportDialog} = require('./ui/exportDialog'); +const {registerTranslations, t} = require('./i18n/translations'); +const actionIconSvg = require('./resources/action_icon.svg'); + +const ACTION_ID = 'eme_export'; +const SETTING_ID = 'eme_enable_customization'; +const EXPERIMENTAL_SETTING_ID = 'eme_enable_experimental'; +const ACTION_ICON = 'data:image/svg+xml,' + encodeURIComponent(actionIconSvg); +let exportAction; +let projectProperty; +let customizationSetting; +let experimentalSetting; + +function customizationEnabled() { + if (customizationSetting && typeof customizationSetting.value === 'boolean') { + return customizationSetting.value; + } + return false; +} + +function experimentalEnabled() { + if (experimentalSetting && typeof experimentalSetting.value === 'boolean') { + return experimentalSetting.value; + } + return false; +} + +function resolveDialogState() { + const storedSettings = BlockbenchAdapter.loadSettings(); + const stats = BlockbenchAdapter.getModelStats(); + const bounds = BlockbenchAdapter.getModelBounds(); + + let settings; + let preset; + let blockPreset; + let modelType; + let customize; + let exportType; + + if (storedSettings) { + settings = deepMerge(getDefaults(), storedSettings); + modelType = settings.modelType || 'entity'; + customize = !!storedSettings.customize; + exportType = storedSettings.exportType + || (storedSettings.exportTarget === 'mod_project' ? 'mod_project' + : 'packs'); + if (modelType === 'block_entity') { + preset = 'custom'; + blockPreset = settings.presetType || 'static'; + } else { + preset = settings.presetType || DEFAULT_PRESET; + blockPreset = 'static'; + } + } else { + preset = PresetDetector.detectPresetType(stats, bounds); + blockPreset = 'static'; + modelType = 'entity'; + settings = applyTemplate(preset); + ModelDimensions.applyModelDimensions(settings, + ModelDimensions.deriveDimensions(bounds, settings.host.bodyType)); + customize = false; + exportType = 'packs'; + const projectName = BlockbenchAdapter.getProjectName(); + if (projectName) { + settings.profileId = ResourceLocation.sanitizeProfileId(projectName); + } + } + + const modelDimensions = ModelDimensions.deriveDimensions(bounds, + settings.host.bodyType); + return { + settings, preset, blockPreset, modelType, customize, exportType, + modelDimensions + }; +} + +function formatIssues(issues) { + return issues.map((issue) => `• ${issue.message}`).join('\n'); +} + +function collectContext() { + const stats = BlockbenchAdapter.getModelStats(); + return Object.assign({}, stats, { + textureCount: BlockbenchAdapter.getTextureCount(), + visibleBoundsManual: true + }); +} + +function runExport(settings, target) { + const context = collectContext(); + const result = Validator.validateSettings(settings, context); + + if (!result.valid) { + Blockbench.showMessageBox({ + title: t('eme.dialog.title'), + message: 'Export blocked. Please fix the following issues:\n\n' + + formatIssues(result.errors) + }); + return; + } + + const doExport = () => performExport(settings, target); + + if (result.warnings.length > 0) { + Blockbench.showMessageBox( + { + title: t('eme.dialog.title'), + message: 'The following warnings were found:\n\n' + formatIssues( + result.warnings) + '\n\nExport anyway?', + buttons: ['Export', 'Cancel'], + confirm: 0, + cancel: 1 + }, + (button) => { + if (button === 0) { + doExport(); + } + } + ); + } else { + doExport(); + } +} + +function performExport(settings, target) { + const options = { + modelBytes: BlockbenchAdapter.getModelBytes(), + textureBytes: BlockbenchAdapter.getTextureBytes() + }; + + BlockbenchAdapter.saveSettings(settings); + + if (target === 'mod_project') { + exportToModProject(settings, options); + } else { + exportToZip(settings, options); + } +} + +function exportToZip(settings, options) { + const bundle = buildPackBundle(settings, options); + BlockbenchAdapter.exportPackBundle(bundle, + `${settings.namespace}_${settings.profileId}_eme`) + .then(() => { + Blockbench.showQuickMessage('Easy Model Entities packs exported', 1500); + }) + .catch((error) => { + Blockbench.showMessageBox({title: 'Export failed', message: String(error)}); + }); +} + +function exportToModProject(settings, options) { + const rootDir = BlockbenchAdapter.pickDirectory( + 'Select src/main/resources directory'); + if (!rootDir) { + return; + } + + const {files} = buildModProjectFiles(settings, options); + const existing = BlockbenchAdapter.listExistingFiles(rootDir, files); + + const write = () => { + try { + BlockbenchAdapter.writeToDirectory(rootDir, files); + Blockbench.showQuickMessage( + 'Easy Model Entities files written to mod project', 1500); + } catch (error) { + Blockbench.showMessageBox( + {title: 'Export failed', message: String(error)}); + } + }; + + if (existing.length > 0) { + Blockbench.showMessageBox( + { + title: 'Overwrite existing files?', + message: `${existing.length} file(s) already exist and will be overwritten:\n\n` + + existing.join('\n'), + buttons: ['Overwrite', 'Cancel'], + confirm: 0, + cancel: 1 + }, + (button) => { + if (button === 0) { + write(); + } + } + ); + } else { + write(); + } +} + +function openDialog() { + if (!Project) { + Blockbench.showQuickMessage('Open a project first', 1500); + return; + } + const state = resolveDialogState(); + openExportDialog({ + settings: state.settings, + preset: state.preset, + blockPreset: state.blockPreset, + modelType: state.modelType, + customize: state.customize, + exportType: state.exportType, + showCustomization: customizationEnabled(), + experimental: experimentalEnabled(), + modelDimensions: state.modelDimensions, + onExport: runExport + }); +} + +BBPlugin.register('easy_model_entities', { + title: 'Easy Model Entities Exporter', + icon: 'icon.png', + author: 'Markus Bordihn', + description: 'Export Blockbench models for the Easy Model Entities mod (Minecraft: Java Edition) as ready-to-install Data Pack and Resource Pack files.', + tags: ['Minecraft: Java Edition', 'Exporter', 'Entity'], + version: '0.1.0', + min_version: '4.9.0', + variant: 'desktop', + has_changelog: true, + website: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities', + repository: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities', + bug_tracker: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities/issues', + onload() { + registerTranslations(); + + projectProperty = new Property(ModelProject, 'object', + BlockbenchAdapter.PROJECT_PROPERTY, { + label: 'Easy Model Entities Settings', + exposed: false, + default: undefined + }); + + customizationSetting = new Setting(SETTING_ID, { + name: t('eme.setting.enable_customization'), + category: 'export', + type: 'toggle', + value: false + }); + + experimentalSetting = new Setting(EXPERIMENTAL_SETTING_ID, { + name: t('eme.setting.enable_experimental'), + category: 'export', + type: 'toggle', + value: false + }); + + exportAction = new Action(ACTION_ID, { + name: 'Export Easy Model Entities Pack', + description: 'Export the current project as Easy Model Entities Data Pack and Resource Pack.', + icon: ACTION_ICON, + click() { + openDialog(); + } + }); + + MenuBar.addAction(exportAction, 'file.export'); + }, + onunload() { + if (exportAction) { + exportAction.delete(); + } + if (customizationSetting) { + customizationSetting.delete(); + } + if (experimentalSetting) { + experimentalSetting.delete(); + } + if (projectProperty) { + projectProperty.delete(); + } + } +}); diff --git a/plugins/easy_model_entities/src/model/ModelDimensions.js b/plugins/easy_model_entities/src/model/ModelDimensions.js new file mode 100644 index 00000000..cba7a9f4 --- /dev/null +++ b/plugins/easy_model_entities/src/model/ModelDimensions.js @@ -0,0 +1,105 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +class ModelDimensions { + static PIXELS_PER_BLOCK = 16; + static FALLBACK = { + width: 0.6, + height: 0.8, + eyeHeight: 0.5, + visibleBoundsWidth: 1.2, + visibleBoundsHeight: 1, + visibleBoundsOffset: [0, 0.5, 0] + }; + + static #EYE_HEIGHT_FACTOR = {biped: 0.9, quadruped: 0.8, static: 0.85}; + static #BOUNDS_MARGIN = 1.1; + + static #round(value, digits) { + const factor = Math.pow(10, digits == null ? 3 : digits); + return Math.round(value * factor) / factor; + } + + static #isUsableBounds(bounds) { + if (!bounds) { + return false; + } + + return ['minX', 'minY', 'minZ', 'maxX', 'maxY', 'maxZ'].every( + (key) => Number.isFinite(bounds[key])); + } + + static deriveDimensions(bounds, bodyType) { + if (!ModelDimensions.#isUsableBounds(bounds)) { + return { + ...ModelDimensions.FALLBACK, + visibleBoundsOffset: ModelDimensions.FALLBACK.visibleBoundsOffset.slice() + }; + } + + const extentX = Math.max(0, bounds.maxX - bounds.minX) + / ModelDimensions.PIXELS_PER_BLOCK; + const extentY = Math.max(0, bounds.maxY - bounds.minY) + / ModelDimensions.PIXELS_PER_BLOCK; + const extentZ = Math.max(0, bounds.maxZ - bounds.minZ) + / ModelDimensions.PIXELS_PER_BLOCK; + + const width = Math.max(extentX, extentZ); + const height = extentY; + if (width <= 0 || height <= 0) { + return { + ...ModelDimensions.FALLBACK, + visibleBoundsOffset: ModelDimensions.FALLBACK.visibleBoundsOffset.slice() + }; + } + + const eyeHeightFactor = + ModelDimensions.#EYE_HEIGHT_FACTOR[bodyType] == null + ? ModelDimensions.#EYE_HEIGHT_FACTOR.static + : ModelDimensions.#EYE_HEIGHT_FACTOR[bodyType]; + + return { + width: ModelDimensions.#round(width), + height: ModelDimensions.#round(height), + eyeHeight: ModelDimensions.#round(height * eyeHeightFactor), + visibleBoundsWidth: ModelDimensions.#round( + width * ModelDimensions.#BOUNDS_MARGIN), + visibleBoundsHeight: ModelDimensions.#round( + height * ModelDimensions.#BOUNDS_MARGIN), + visibleBoundsOffset: [0, ModelDimensions.#round(height / 2), 0] + }; + } + + // mutates settings in-place and returns it + static applyModelDimensions(settings, modelDimensions) { + if (!modelDimensions) { + return settings; + } + + settings.dimensions.width = modelDimensions.width; + settings.dimensions.height = modelDimensions.height; + settings.dimensions.eyeHeight = modelDimensions.eyeHeight; + settings.rendering.visibleBoundsWidth = modelDimensions.visibleBoundsWidth; + settings.rendering.visibleBoundsHeight = modelDimensions.visibleBoundsHeight; + settings.rendering.visibleBoundsOffset = modelDimensions.visibleBoundsOffset.slice(); + return settings; + } +} + +module.exports = {ModelDimensions}; diff --git a/plugins/easy_model_entities/src/model/PresetDetector.js b/plugins/easy_model_entities/src/model/PresetDetector.js new file mode 100644 index 00000000..658dbbc1 --- /dev/null +++ b/plugins/easy_model_entities/src/model/PresetDetector.js @@ -0,0 +1,110 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Suggests the most likely ModelPresetType from a model's bone names and bounds. +// Pure and side-effect free so it can be unit tested in isolation. Always +// suggests a "still" variant (the safe default); the user can switch to a +// moving variant in the dialog. + +class PresetDetector { + static #CUBE_RATIO = 1.4; + + static #names(stats) { + const boneNames = (stats && stats.boneNames) || []; + return boneNames.map((name) => String(name).toLowerCase()); + } + + static #any(names, ...needles) { + return names.some( + (name) => needles.some((needle) => name.includes(needle))); + } + + static #count(names, needle) { + return names.filter((name) => name.includes(needle)).length; + } + + static #extents(bounds) { + if (!bounds) { + return null; + } + const keys = ['minX', 'minY', 'minZ', 'maxX', 'maxY', 'maxZ']; + if (!keys.every((key) => Number.isFinite(bounds[key]))) { + return null; + } + return { + x: Math.max(0, bounds.maxX - bounds.minX), + y: Math.max(0, bounds.maxY - bounds.minY), + z: Math.max(0, bounds.maxZ - bounds.minZ) + }; + } + + // Returns {presetType, reason}. + static detect(stats, bounds) { + const names = PresetDetector.#names(stats); + const hasWings = PresetDetector.#any(names, 'wing'); + const hasArms = PresetDetector.#any(names, 'arm'); + const legCount = PresetDetector.#count(names, 'leg'); + const hasFrontBackLegs = names.some( + (name) => name.includes('leg') + && (name.includes('front') || name.includes('back'))); + + if (hasWings) { + return hasArms + ? {presetType: 'winged_humanoid_still', reason: 'wings + arms'} + : {presetType: 'winged_still', reason: 'wing bones'}; + } + + if (PresetDetector.#any(names, 'fin', 'fluke', 'fish')) { + return {presetType: 'aquatic_still', reason: 'fin/fish bones'}; + } + + if (legCount >= 6 || PresetDetector.#any(names, 'spider', 'insect', + 'mandible')) { + return {presetType: 'arthropod_still', reason: 'six or more legs'}; + } + + if (hasFrontBackLegs || legCount === 4) { + return {presetType: 'quadruped_still', reason: 'four legs (front/back)'}; + } + + if (hasArms) { + return {presetType: 'humanoid_still', reason: 'arm bones'}; + } + + const extents = PresetDetector.#extents(bounds); + if (extents && extents.x > 0 && extents.y > 0 && extents.z > 0) { + const max = Math.max(extents.x, extents.y, extents.z); + const min = Math.min(extents.x, extents.y, extents.z); + if (max / min <= PresetDetector.#CUBE_RATIO) { + return {presetType: 'cuboid_still', reason: 'cube-like proportions'}; + } + if (extents.y < Math.max(extents.x, extents.z) * 0.5) { + return {presetType: 'aquatic_still', reason: 'flat proportions'}; + } + } + + return {presetType: 'statue', reason: 'no distinguishing limbs'}; + } + + static detectPresetType(stats, bounds) { + return PresetDetector.detect(stats, bounds).presetType; + } +} + +module.exports = {PresetDetector}; diff --git a/plugins/easy_model_entities/src/model/Validator.js b/plugins/easy_model_entities/src/model/Validator.js new file mode 100644 index 00000000..bd129e47 --- /dev/null +++ b/plugins/easy_model_entities/src/model/Validator.js @@ -0,0 +1,250 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {ResourceLocation} = require('../utils/ResourceLocation'); +const { + PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + MODEL_TYPE_BLOCK_ENTITY +} = require('./presetTypes'); + +class Validator { + static BUDGETS = { + maxTextureSize: 2048, + maxModelFileSize: 2 * 1024 * 1024, + maxCubeCount: 512, + maxBoneCount: 128, + maxHierarchyDepth: 32 + }; + + static REQUIRED_BODY_PARTS = { + quadruped: ['root', 'body', 'head', 'front_left_leg', 'front_right_leg', + 'back_left_leg', 'back_right_leg'], + biped: ['root', 'head', 'body', 'left_arm', 'right_arm', 'left_leg', + 'right_leg'], + static: [] + }; + + static #isFiniteNumber(value) { + return typeof value === 'number' && Number.isFinite(value); + } + + static #validateNumeric(errors, label, value) { + if (!Validator.#isFiniteNumber(value)) { + errors.push({ + code: 'INVALID_NUMERIC', + message: `${label} must be a finite number` + }); + return false; + } + return true; + } + + static validateSettings(settings, context) { + const errors = []; + const warnings = []; + const ctx = context || {}; + + if (!ResourceLocation.isValidNamespace(settings.namespace)) { + errors.push({ + code: 'INVALID_NAMESPACE', + message: `Invalid namespace: ${settings.namespace}` + }); + } + if (!ResourceLocation.isValidPath(settings.profileId)) { + errors.push({ + code: 'INVALID_PROFILE_ID', + message: `Invalid profile ID: ${settings.profileId}` + }); + } + + const id = `${settings.namespace}:${settings.profileId}`; + if ( + ResourceLocation.isValidNamespace(settings.namespace) && + ResourceLocation.isValidPath(settings.profileId) && + !ResourceLocation.isValidResourceLocation(id) + ) { + errors.push({ + code: 'INVALID_RESOURCE_LOCATION', + message: `Invalid resource location: ${id}` + }); + } + const blockEntity = settings.modelType === MODEL_TYPE_BLOCK_ENTITY; + + // Block entity host type is derived by the mod; only entities carry one. + if (!blockEntity + && !ResourceLocation.isValidResourceLocation( + settings.host.entityType)) { + errors.push({ + code: 'INVALID_RESOURCE_LOCATION', + message: `Invalid host entity type: ${settings.host.entityType}` + }); + } + const validPresets = blockEntity ? BLOCK_ENTITY_PRESET_TYPES : PRESET_TYPES; + if (!validPresets.includes(settings.presetType)) { + errors.push({ + code: 'INVALID_PRESET_TYPE', + message: `Invalid preset type: ${settings.presetType}` + }); + } + + const {width, height, eyeHeight} = settings.dimensions; + const widthValid = Validator.#validateNumeric(errors, 'dimensions.width', + width); + const heightValid = Validator.#validateNumeric(errors, 'dimensions.height', + height); + const eyeValid = Validator.#validateNumeric(errors, 'dimensions.eye_height', + eyeHeight); + if (widthValid && width <= 0) { + errors.push({ + code: 'INVALID_DIMENSIONS', + message: 'width must be greater than 0' + }); + } + if (heightValid && height <= 0) { + errors.push({ + code: 'INVALID_DIMENSIONS', + message: 'height must be greater than 0' + }); + } + if (eyeValid && eyeHeight < 0) { + errors.push({ + code: 'INVALID_DIMENSIONS', + message: 'eye_height must not be negative' + }); + } + if (eyeValid && heightValid && eyeHeight > height) { + errors.push({ + code: 'INVALID_DIMENSIONS', + message: 'eye_height must not exceed height' + }); + } + + Validator.#validateNumeric(errors, 'movement.speed', + settings.movement.speed); + Validator.#validateNumeric(errors, 'movement.step_height', + settings.movement.stepHeight); + + if (ctx.hasModel === false) { + errors.push( + {code: 'MISSING_MODEL', message: 'No model present in project'}); + } + if (ctx.hasTexture === false) { + errors.push( + {code: 'MISSING_TEXTURE', message: 'No texture present in project'}); + } + + if (Validator.#isFiniteNumber(ctx.textureCount) && ctx.textureCount > 1) { + warnings.push({ + code: 'MULTIPLE_TEXTURES', + message: 'Project has multiple textures; only the active texture is exported' + }); + } + + if (Validator.#isFiniteNumber(ctx.textureWidth) + && Validator.#isFiniteNumber(ctx.textureHeight)) { + if (ctx.textureWidth > Validator.BUDGETS.maxTextureSize + || ctx.textureHeight > Validator.BUDGETS.maxTextureSize) { + warnings.push({ + code: 'LARGE_TEXTURE', + message: `Texture larger than ${Validator.BUDGETS.maxTextureSize}x${Validator.BUDGETS.maxTextureSize}` + }); + } + } + if (Validator.#isFiniteNumber(ctx.modelFileSize) && ctx.modelFileSize + > Validator.BUDGETS.maxModelFileSize) { + warnings.push( + {code: 'LARGE_MODEL', message: 'Model file larger than 2 MB'}); + } + if (Validator.#isFiniteNumber(ctx.cubeCount) && ctx.cubeCount + > Validator.BUDGETS.maxCubeCount) { + warnings.push({ + code: 'HIGH_CUBE_COUNT', + message: `More than ${Validator.BUDGETS.maxCubeCount} cubes` + }); + } + if (Validator.#isFiniteNumber(ctx.boneCount) && ctx.boneCount + > Validator.BUDGETS.maxBoneCount) { + warnings.push({ + code: 'HIGH_BONE_COUNT', + message: `More than ${Validator.BUDGETS.maxBoneCount} bones` + }); + } + if (Validator.#isFiniteNumber(ctx.hierarchyDepth) && ctx.hierarchyDepth + > Validator.BUDGETS.maxHierarchyDepth) { + warnings.push({ + code: 'DEEP_HIERARCHY', + message: `Hierarchy deeper than ${Validator.BUDGETS.maxHierarchyDepth}` + }); + } + + const missingParts = Validator.getMissingBodyParts(settings.host.bodyType, + ctx.boneNames || []); + missingParts.forEach((part) => { + warnings.push({ + code: 'MISSING_BODY_PART', + message: `Missing recommended body part: ${part}` + }); + }); + + if (ctx.visibleBoundsManual === false) { + warnings.push({ + code: 'VISIBLE_BOUNDS_DEFAULT', + message: 'Visible bounds were not set manually' + }); + } + + return {errors, warnings, valid: errors.length === 0}; + } + + static getMissingBodyParts(bodyType, boneNames) { + const required = Validator.REQUIRED_BODY_PARTS[bodyType] || []; + const present = new Set( + (boneNames || []).map((name) => String(name).toLowerCase())); + return required.filter((part) => !present.has(part)); + } + + static validateOutputPath(outputRoot, relativePath) { + if (typeof relativePath !== 'string' || relativePath.length === 0) { + return { + valid: false, + code: 'INVALID_OUTPUT_PATH', + message: 'Empty output path' + }; + } + const normalized = relativePath.replaceAll('\\', '/'); + if (normalized.includes('..')) { + return { + valid: false, + code: 'PATH_TRAVERSAL', + message: 'Path traversal detected' + }; + } + if (/^([a-zA-Z]:\/|\/)/.test(normalized)) { + return { + valid: false, + code: 'ABSOLUTE_PATH', + message: 'Absolute paths are not allowed' + }; + } + return {valid: true, root: outputRoot, path: normalized}; + } +} + +module.exports = {Validator}; diff --git a/plugins/easy_model_entities/src/model/presetTypes.js b/plugins/easy_model_entities/src/model/presetTypes.js new file mode 100644 index 00000000..967c01c5 --- /dev/null +++ b/plugins/easy_model_entities/src/model/presetTypes.js @@ -0,0 +1,346 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Mirrors de.markusbordihn.easymodelentities.profile.ModelPresetType and the +// per-preset defaults derived by EasyModelProfileParser / ModelRenderProfileParser. +// This file is the single source of truth the plugin uses to compute minimal +// (diff-based) profiles, so it must stay in sync with the mod. + +const SCHEMA_VERSION = '0.1.0'; + +// Mirrors de.markusbordihn.easymodelentities.data.profile.ModelType. +const MODEL_TYPE_ENTITY = 'entity'; +const MODEL_TYPE_BLOCK_ENTITY = 'block_entity'; +const MODEL_TYPES = [MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY]; + +const GROUND_ENTITY = 'easy_model_entities:ground_entity'; +const STATIC_ENTITY = 'easy_model_entities:static_entity'; + +const FALLBACK_DIMENSIONS = {width: 0.6, height: 1.8, eyeHeight: 1.62}; +const BLOCK_ENTITY_DIMENSIONS = {width: 1.0, height: 1.0, eyeHeight: 0.5}; + +// All ModelPresetType values in serialized form. +const PRESET_TYPES = [ + 'custom', + 'static', + 'statue', + 'humanoid_still', + 'humanoid_wandering', + 'quadruped_still', + 'quadruped_wandering', + 'aquatic_still', + 'aquatic_swimming', + 'winged_still', + 'winged_wandering', + 'winged_humanoid_still', + 'winged_humanoid_wandering', + 'arthropod_still', + 'arthropod_wandering', + 'cuboid_still', + 'cuboid_hopping', + 'floating_still' +]; + +// All ModelBlockEntityPresetType values in serialized form. +const BLOCK_ENTITY_PRESET_TYPES = [ + 'static', + 'ticking', + 'animated', + 'animated_randomly' +]; + +// Presets the mod marks as stable for pack authors; experimental ones are hidden +// in the UI behind the "Show experimental presets" setting. +const STABLE_PRESET_TYPES = new Set([ + 'static', + 'statue', + 'humanoid_still', + 'humanoid_wandering', + 'quadruped_still', + 'quadruped_wandering', + // Block entity stable presets. + 'animated', + 'animated_randomly' +]); + +// Presets selectable as a starting point in the UI (custom handled separately). +const SELECTABLE_PRESET_TYPES = PRESET_TYPES.filter((id) => id !== 'custom'); + +function isStablePreset(presetType) { + return STABLE_PRESET_TYPES.has(presetType); +} + +function isCustom(presetType) { + return presetType === 'custom'; +} + +function isStill(presetType) { + return String(presetType).endsWith('_still'); +} + +function isMoving(presetType) { + return String(presetType).endsWith('_wandering') + || presetType === 'aquatic_swimming' + || presetType === 'cuboid_hopping'; +} + +function bodyType(presetType) { + switch (presetType) { + case 'humanoid_still': + case 'humanoid_wandering': + return 'biped'; + case 'quadruped_still': + case 'quadruped_wandering': + return 'quadruped'; + case 'aquatic_still': + case 'aquatic_swimming': + return 'aquatic'; + case 'winged_still': + case 'winged_wandering': + return 'winged'; + case 'winged_humanoid_still': + case 'winged_humanoid_wandering': + return 'winged_humanoid'; + case 'arthropod_still': + case 'arthropod_wandering': + return 'arthropod'; + case 'cuboid_still': + case 'cuboid_hopping': + return 'cuboid'; + case 'floating_still': + return 'floating'; + default: + return 'static'; + } +} + +function movementType(presetType) { + return isMoving(presetType) ? 'ground' : 'static'; +} + +function entityType(presetType) { + return isMoving(presetType) || presetType === 'static' ? GROUND_ENTITY + : STATIC_ENTITY; +} + +function presetDimensions(presetType) { + switch (presetType) { + case 'quadruped_still': + case 'quadruped_wandering': + return {width: 0.9, height: 0.9, eyeHeight: 0.6}; + case 'aquatic_still': + case 'aquatic_swimming': + return {width: 0.7, height: 0.4, eyeHeight: 0.25}; + case 'winged_still': + case 'winged_wandering': + return {width: 0.6, height: 0.9, eyeHeight: 0.6}; + case 'winged_humanoid_still': + case 'winged_humanoid_wandering': + return {width: 0.6, height: 0.8, eyeHeight: 0.6}; + case 'arthropod_still': + case 'arthropod_wandering': + return {width: 1.4, height: 0.9, eyeHeight: 0.45}; + case 'cuboid_still': + case 'cuboid_hopping': + case 'floating_still': + return {width: 1.0, height: 1.0, eyeHeight: 0.5}; + default: + return {...FALLBACK_DIMENSIONS}; + } +} + +// [visibleBoundsWidth, visibleBoundsHeight, visibleBoundsOffsetY, shadowRadius] +function presetRenderBounds(presetType) { + switch (presetType) { + case 'quadruped_still': + case 'quadruped_wandering': + return [0.9, 0.9, 0.45, 0.45]; + case 'aquatic_still': + case 'aquatic_swimming': + return [0.7, 0.4, 0.2, 0.25]; + case 'winged_still': + case 'winged_wandering': + return [0.6, 0.9, 0.45, 0.25]; + case 'winged_humanoid_still': + case 'winged_humanoid_wandering': + return [0.6, 0.8, 0.4, 0.25]; + case 'arthropod_still': + case 'arthropod_wandering': + return [1.4, 0.9, 0.45, 0.7]; + case 'cuboid_still': + case 'cuboid_hopping': + case 'floating_still': + return [1.0, 1.0, 0.5, 0.5]; + default: + return [0.6, 1.8, 0.9, 0.3]; + } +} + +// Behavior mode the mod derives for a preset, given the resolved movement type +// (only relevant for the custom preset where movement type can vary). +function behaviorModeFor(presetType, move) { + if (isMoving(presetType)) { + return 'ambient'; + } + if (isStill(presetType)) { + return 'idle_only'; + } + if (presetType === 'custom') { + return move === 'ground' ? 'idle_only' : 'static'; + } + return 'static'; +} + +function behaviorMode(presetType) { + return behaviorModeFor(presetType, movementType(presetType)); +} + +// Movement defaults the mod derives from the resolved movement type. +function movementDefaults(presetType, move) { + const ground = move === 'ground'; + return { + speed: ground ? 0.22 : 0, + stepHeight: ground ? 0.6 : 0, + gravity: presetType === 'static' || ground + }; +} + +function animationMode(presetType) { + return presetType === 'custom' || presetType === 'static' + || presetType === 'statue' ? 'none' : 'automatic'; +} + +// Animation mode the mod derives for a block entity preset. +function blockEntityAnimationMode(presetType) { + switch (presetType) { + case 'animated': + return 'automatic'; + case 'animated_randomly': + return 'random_idle'; + default: + return 'none'; + } +} + +// Block entities ignore movement/behavior/attributes; the host is always a +// static cuboid block. Render bounds default to a 1x1x1 block. +function blockEntityPresetDefaults(presetType) { + return { + schemaVersion: SCHEMA_VERSION, + modelType: MODEL_TYPE_BLOCK_ENTITY, + presetType: presetType, + version: '', + host: {entityType: '', movementType: 'static', bodyType: 'static'}, + dimensions: {...BLOCK_ENTITY_DIMENSIONS}, + movement: {speed: 0, stepHeight: 0, gravity: false}, + behavior: {mode: 'static', lookAtPlayers: false, randomStroll: false}, + attributes: {maxHealth: 10, movementSpeed: 0, followRange: 16}, + rendering: { + scale: 1, + shadowRadius: 0.5, + visibleBoundsWidth: 1.0, + visibleBoundsHeight: 1.0, + visibleBoundsOffset: [0, 0.5, 0] + }, + animation: { + mode: blockEntityAnimationMode(presetType), + swingSpeed: 1, + walkSpeedMultiplier: 1 + } + }; +} + +// Full settings object matching the mod defaults for the given preset type. +function presetDefaults(presetType, modelType) { + if (modelType === MODEL_TYPE_BLOCK_ENTITY) { + return blockEntityPresetDefaults(presetType); + } + const move = movementType(presetType); + const ground = move === 'ground'; + const speed = ground ? 0.22 : 0; + const stepHeight = ground ? 0.6 : 0; + const gravity = presetType === 'static' || ground; + const mode = behaviorMode(presetType); + const lookAtPlayers = mode === 'idle_only' || mode === 'ambient'; + const randomStroll = ground && mode === 'ambient'; + const dimensions = presetDimensions(presetType); + const [boundsWidth, boundsHeight, boundsOffsetY, shadowRadius] = + presetRenderBounds(presetType); + + return { + schemaVersion: SCHEMA_VERSION, + modelType: MODEL_TYPE_ENTITY, + presetType: presetType, + version: '', + host: { + entityType: entityType(presetType), + movementType: move, + bodyType: bodyType(presetType) + }, + dimensions: dimensions, + movement: {speed: speed, stepHeight: stepHeight, gravity: gravity}, + behavior: { + mode: mode, + lookAtPlayers: lookAtPlayers, + randomStroll: randomStroll + }, + attributes: {maxHealth: 10, movementSpeed: speed, followRange: 16}, + rendering: { + scale: 1, + shadowRadius: shadowRadius, + visibleBoundsWidth: boundsWidth, + visibleBoundsHeight: boundsHeight, + visibleBoundsOffset: [0, boundsOffsetY, 0] + }, + animation: { + mode: animationMode(presetType), + swingSpeed: 1, + walkSpeedMultiplier: 1 + } + }; +} + +module.exports = { + SCHEMA_VERSION, + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY, + MODEL_TYPES, + GROUND_ENTITY, + STATIC_ENTITY, + PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + SELECTABLE_PRESET_TYPES, + STABLE_PRESET_TYPES, + isStablePreset, + isCustom, + isStill, + isMoving, + bodyType, + movementType, + entityType, + behaviorMode, + behaviorModeFor, + movementDefaults, + animationMode, + blockEntityAnimationMode, + presetDimensions, + presetRenderBounds, + presetDefaults, + blockEntityPresetDefaults +}; diff --git a/plugins/easy_model_entities/src/model/templates.js b/plugins/easy_model_entities/src/model/templates.js new file mode 100644 index 00000000..49e24e4b --- /dev/null +++ b/plugins/easy_model_entities/src/model/templates.js @@ -0,0 +1,74 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const { + SCHEMA_VERSION, + PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + SELECTABLE_PRESET_TYPES, + MODEL_TYPE_ENTITY, + presetDefaults +} = require('./presetTypes'); + +const DEFAULT_PRESET = 'statue'; + +function deepMerge(target, source) { + const result = Array.isArray(target) ? target.slice() : ({ + ...target + }); + Object.keys(source).forEach((key) => { + const value = source[key]; + if (value && typeof value === 'object' && !Array.isArray(value)) { + result[key] = deepMerge(result[key] || {}, value); + } else if (Array.isArray(value)) { + result[key] = value.slice(); + } else { + result[key] = value; + } + }); + return result; +} + +function getDefaults() { + return deepMerge(presetDefaults(DEFAULT_PRESET), { + namespace: 'example_org', + profileId: 'entity', + targetVersion: '1.20.1' + }); +} + +// Full settings object for a preset type, including the identity fields. The +// model type disambiguates presets that exist for both entities and block +// entities (e.g. "static"). +function applyTemplate(presetType, modelType) { + return deepMerge(getDefaults(), + presetDefaults(presetType, modelType || MODEL_TYPE_ENTITY)); +} + +module.exports = { + SCHEMA_VERSION, + DEFAULT_PRESET, + PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + SELECTABLE_PRESET_TYPES, + getDefaults, + applyTemplate, + presetDefaults, + deepMerge +}; diff --git a/plugins/easy_model_entities/src/model/versionMatrix.js b/plugins/easy_model_entities/src/model/versionMatrix.js new file mode 100644 index 00000000..c1456920 --- /dev/null +++ b/plugins/easy_model_entities/src/model/versionMatrix.js @@ -0,0 +1,85 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const DEFAULT_VERSION_ID = '1.20.1'; + +const VERSIONS = [ + { + id: '1.20.1', + label: 'Minecraft 1.20.1', + dataFormat: 15, + resourceFormat: 15, + enabled: true + }, + { + id: '1.21.1', + label: 'Minecraft 1.21.1', + dataFormat: 48, + resourceFormat: 34, + enabled: false + }, + { + id: '1.21.11', + label: 'Minecraft 1.21.11', + dataFormat: null, + resourceFormat: null, + enabled: false + }, + { + id: '26.1.2', + label: 'Minecraft 26.1.2', + dataFormat: null, + resourceFormat: null, + enabled: false + } +]; + +function getVersions() { + return VERSIONS.map((version) => ({...version})); +} + +function getVersion(id) { + return VERSIONS.find((version) => version.id === id) || null; +} + +function getEnabledVersions() { + return getVersions().filter((version) => version.enabled); +} + +function getDefaultVersionId() { + return DEFAULT_VERSION_ID; +} + +function getPackFormats(id) { + const version = getVersion(id); + if (!version?.enabled) { + return null; + } + return { + dataFormat: version.dataFormat, + resourceFormat: version.resourceFormat + }; +} + +module.exports = { + getVersions, + getEnabledVersions, + getDefaultVersionId, + getPackFormats +}; diff --git a/plugins/easy_model_entities/src/package-lock.json b/plugins/easy_model_entities/src/package-lock.json new file mode 100644 index 00000000..1f69970a --- /dev/null +++ b/plugins/easy_model_entities/src/package-lock.json @@ -0,0 +1,6031 @@ +{ + "name": "easy_model_entities", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "easy_model_entities", + "version": "0.1.0", + "devDependencies": { + "blockbench-types": "^4.9.0", + "copy-webpack-plugin": "^12.0.2", + "jest": "^29.7.0", + "webpack": "^5.80.0", + "webpack-cli": "^5.1.4" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", + "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/console/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/core/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jquery": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.34.tgz", + "integrity": "sha512-3m3939S3erqmTLJANS/uy0B6V7BorKx7RorcGZVjZ62dF5PAGbKEDZK1CuLtKombJkFA2T1jl8LAIIs7IV6gBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/sizzle": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prismjs": { + "version": "1.26.6", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/sizzle": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz", + "integrity": "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.129.2", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.129.2.tgz", + "integrity": "sha512-fZDNRtUFnq3baNjDiAzi4QoHYcczWtEdsduH/tpiwk91fbVk8COJiT/iLoN3tIOjLKMySoYsjUfP0VTmUfZKLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/tinycolor2": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.6.tgz", + "integrity": "sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "2.7.14", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz", + "integrity": "sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.18.4", + "postcss": "^8.4.14", + "source-map": "^0.6.1" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", + "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", + "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", + "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.15.0" + }, + "peerDependencies": { + "webpack": "5.x.x", + "webpack-cli": "5.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-jest/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.37", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", + "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/blockbench-types": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/blockbench-types/-/blockbench-types-4.12.2.tgz", + "integrity": "sha512-OkqlV5RO5PAV7fkix9MfVZD1bomJpw33q7vUwA0Otekd3N2Cf+f0im0diHjsZiFACmvApMfa60jajNlD6Cqj3A==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7", + "@types/dompurify": "^3.0.5", + "@types/jquery": "^3.5.32", + "@types/prismjs": "^1.26.0", + "@types/three": "^0.129.2", + "@types/tinycolor2": "^1.4.6", + "dompurify": "^3.0.1", + "electron": "^33.3.1", + "prismjs": "^1.29.0", + "tinycolor2": "^1.6.0", + "typescript": "^4.9.5", + "vue": "2.7.14", + "wintersky": "^1.3.0" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dedent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dompurify": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.10.tgz", + "integrity": "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==", + "dev": true, + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/electron": { + "version": "33.4.11", + "resolved": "https://registry.npmjs.org/electron/-/electron-33.4.11.tgz", + "integrity": "sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^20.9.0", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.372", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.372.tgz", + "integrity": "sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.0.tgz", + "integrity": "sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", + "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.3", + "ignore": "^7.0.3", + "path-type": "^6.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-config/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/loader-runner": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/molangjs": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/molangjs/-/molangjs-1.7.0.tgz", + "integrity": "sha512-slta4hSuftsCvirnepZ2L46k/uEfAmaE0o9wo/b6oES7p9dh+N7HCUErFEsoH3dGxOoQ86pW2A+GRuMWl/rlOA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.47", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", + "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", + "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/roarr/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-error/node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.48.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", + "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.1.tgz", + "integrity": "sha512-201R5j+sJpK8nFWwKVyNfZot8FaJbLZDq5evriVzbV1wDtSXDjRUDRfJzHpAaxFDMEhsZL1QkeqM61wgsS3KaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/three": { + "version": "0.134.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.134.0.tgz", + "integrity": "sha512-LbBerg7GaSPjYtTOnu41AMp7tV6efUNR3p4Wk5NzkSsNTBuA5mDGOfwwZL1jhhVMLx9V20HolIUo0+U3AXehbg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/vue": { + "version": "2.7.14", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.14.tgz", + "integrity": "sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==", + "deprecated": "Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details.", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-sfc": "2.7.14", + "csstype": "^3.1.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/watchpack": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", + "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.107.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.107.2.tgz", + "integrity": "sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.22.0", + "es-module-lexer": "^2.1.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "loader-runner": "^4.3.2", + "mime-db": "^1.54.0", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.5.0", + "watchpack": "^2.5.1", + "webpack-sources": "^3.5.0" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", + "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^2.1.1", + "@webpack-cli/info": "^2.0.2", + "@webpack-cli/serve": "^2.0.5", + "colorette": "^2.0.14", + "commander": "^10.0.1", + "cross-spawn": "^7.0.3", + "envinfo": "^7.7.3", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^5.7.3" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "5.x.x" + }, + "peerDependenciesMeta": { + "@webpack-cli/generators": { + "optional": true + }, + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz", + "integrity": "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/wintersky": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/wintersky/-/wintersky-1.3.3.tgz", + "integrity": "sha512-1rT84FrUFu3rWLsT6WBPUFm0/zxCFrRR86D5zfzeS1XKAp3LLiQligXK9830BvAcVrAbsvBhm9vlaHsFOHkU5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "molangjs": "^1.6.6", + "three": "^0.134.0", + "tinycolor2": "^1.4.2" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/plugins/easy_model_entities/src/package.json b/plugins/easy_model_entities/src/package.json new file mode 100644 index 00000000..e4d98f37 --- /dev/null +++ b/plugins/easy_model_entities/src/package.json @@ -0,0 +1,28 @@ +{ + "name": "easy_model_entities", + "version": "0.1.0", + "description": "Export Blockbench projects into Easy Model Entities Data Pack and Resource Pack files.", + "author": "Markus Bordihn", + "private": true, + "main": "index.js", + "scripts": { + "build": "webpack", + "test": "jest" + }, + "devDependencies": { + "blockbench-types": "^4.9.0", + "copy-webpack-plugin": "^12.0.2", + "jest": "^29.7.0", + "webpack": "^5.80.0", + "webpack-cli": "^5.1.4" + }, + "jest": { + "testEnvironment": "node", + "testMatch": [ + "**/tests/**/*.test.js" + ], + "transform": { + "\\.md$": "/tests/mdTransform.js" + } + } +} diff --git a/plugins/easy_model_entities/src/resources/action_icon.svg b/plugins/easy_model_entities/src/resources/action_icon.svg new file mode 100644 index 00000000..a67dd1ec --- /dev/null +++ b/plugins/easy_model_entities/src/resources/action_icon.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/easy_model_entities/src/resources/icon.png b/plugins/easy_model_entities/src/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7e7bf10a3ea50f305adc8969e8010b9b87a8b375 GIT binary patch literal 5255 zcmV;26nN{2P)_{r~^}Yp5$)nkZ3`Cu*lE03Rs-{rzsLELoZ;x7knp{r+vKD_xu@TbwBV|Npt# zQC^)XZK^9(mMH)J{|_}K{QUeMRUz;8_ezT=02(U(|Ni>@{`vd-1t%y+hbLs9DLQ*5 z^!fQzlPBf!^AtQM01PK?tt?uXDF9(=PmU-6YIF!LCp2><|N8pO=;{J?fdFuN;o>_xQcv;s9lD02C-0L?`O>^=hatICv%~U?t!0@bmrtf4jW~e1!m4 zVrZl)V4^8Nbto}wCIAy1{`K|N?CincR)D!QWTh$+Kq47TAOHOSn9k97x4jWBC(z^M zIGLbDfG0hFCIA8@000o``1{4;HZgUsFX-#+QJP-kQhDMW?S?rK$i?T)f>;pUXr58X+4`Alml% zsO9Ro<>-LJ$RL!V8jqJAhmQe-j>qFyk-O z@AFQvwmYJ!MWClCo~EhIPE3C)R-7pta0Gkp*RhjJbJRZ*$AwY;dhv zoVP||kvFE#F(zy!B-Gc##;lB;oPV5(`@R}{xdE?}M*fkHwGWHCcRQj(Mr_K*#g77%136p__}$P@|+ zxDY{cwC=6dR$E7FtKCz(_uhN&z0Q62y}YcpU%#*IU&Fh(FFC(^&OP_!=7JNC$LI4H zacw>bx9#t6_emdVk+$`FNzeZQaQsHgw-VWaSo8K^5NG560B}aRx7mis*1V&8j$!{j z0ADzW{S?}o2We@FtI>;ITP18<8dJy6aS|AV#x$EBa zu`#V-j{K%?_P?-LS6*JOc<@zIAwp*3T$s>3FFyLzH8vrF?xOW^MGgQ$DazkzjLXpO z9+x2`zG=gwVGZZqv*%T8+T{PheL=_p;OiHU-@^gS#`p{3>JqKX6@=ZXWlvw>300+_v<>?V-7wDg+PSY!Ys^E{}Jd+y4}w>EQq%y*8uB8USNA7&UQ| zk9Xxq@MPrP%13WYy6w$3pRb=j^UZ@WBNaD3aG!j$CE&gN%6qIz1;wNkwe#p!Idm~& z&x>uT%g`9oThhSx(0JD{LY|&tF#w)_zP|s~S+w3b78<>&V%3AMY=(D`bSb>B|7o^_ z5`aWPo<-=lu9x94NWgx6Z=5E>SlXRW!5f9bv^C>*k5|e7FzeCh>sR*AVgUWK`p?jQ zeDLL~a>U1g)C+IkLC*lO>=Yzx?Q7Cg6rWGwD1b59&9#%-vAEs$R*|USK0ZHhyZ*rA z%K&hpe&sA%O?O0BMEhU;@y#ZLB=msAsPN!>7v6M2P89X->qoW4iS*d<9pPQtj127p zdg!c#6yIq$G4;Spvdt@w+XGgtxb@6Kx37w>P<*s@&ijX?ZsGmNdM8$c`f^!KyoFCtp8=UH9UGnbO2 zb$ajki|5WgeK3Xx9!M$uZeyJB1fKvxwq~?;>4#W=G-z$t-7lx6hWJu6VEXuNw>|tY z%!3VJ#fm-Cx9gs}lK}49f9~8v2Tdksp?S(Cv`2T7h@-?lLzjN^$dfH=aP>KG_kp!0 z%ZfVp;b)&sx&h2%tFz$$tjnihfqZ^iZr1I*w0hT2uMFSsl;fdJ4KPalM3Z~nj` zYk_I`rcdIQlwUhFHPu_3hNP5b@!Rtj44V-i`ilOBV0V$5$i>agZD8J&momkZFBapL z6@Wxto@!V}MjWEAT)V?)gd$-H`06`0<`=EFY{O-@PM>!bxw(6QeGGVx0L$Q{GJkg^m5_z=Q^ZEv{jqLp(S0=&H37`eFkfd}-fz+7P= zgb0KIc-S?k+YaZ`%@D&n0;ty0FAb_`GfeH;62t(0`EV8;oDD0cuL{U2C}-+p~^0Uc2_D? zo6o=7umN&1t02>P23CMO2T-yA^wjZZk01a2i+jKWaERi`WfHs!hzF6^62L^UP{>ZM zg!1GiF2CNe?$Dm;rC9}F*x8vpum>(60+_duNqY%j!rLr?=wJZigIHto#K^}9pt^|z z(6tf8tX=op!%GWhgJBNfZUOKi9*{ZQd=r?p-M$kKghJeB3bNdalSbv}zNO9}Fiwli z0T>6lxXdR2tIdENKoY_Ng6sg0#3z?}a{$@{4@VhGg$c)$%xEEYQg@Iy2VAa^S}y#5I4!Rm@Hxdb6}z2 zYg-RbepstHateI7hW0>xOb4UZxsyOeVf#^w-M|9?Sa0QR0c0Jp9&{1FJ0%p9?Z$VH z9^mmd({r%b-yZ|iEC4GT0P`IIY`5P+^Ez0xmh?VZO}|`z6l#>XxIGB~w8?pDvfvc) zQStHf=DWc{3*{ymUmXC*O11$sl9%4n%H!QsjOQR5$3&oD06r>#q9a@6@+SbU$+(rM zVXA4-1fL%h10kv*z+38sLh!&D0K@}kk`ol+Sst`t4nDFjJ@x?bfCVhSmjFhYJ@;z> zxM>cAhym!%()OLzt_3V$o+Q&K3Y3#O-G0Ku=#x3r6`A4d}yJgaL~c*nWo#w(mV-UwE(?aHS)F&6n;( z?QXgyUe09C{gJg*2vO86>tP)LV77C%Psie(oN0kb=F>dbC8ro0E&2>LI7pRKVIeIswRN>QQ(0s!0y~cGC9`P z6av;-UZ(phN1B}S*l(hh&CW@)X@PZI<>Cd3ZBT|FgGAxUI@JevT8GFU06G+z{=|+F z9;($-bWKm}o+zqGLo0w6dbe_f7ppU4dtx^1BX)s3H-8~|*cLPOnc=|}GGIq8cK z(-(AjWZ96fNi0t5@do4G-ZN(kRQ|Bm{Iak_%IpNy#s@zA_(v169Bx;%J#dFT!1|z)%9AGE zZOG81pOgvbbo&mSj*Eovw3ONNv{UU6A}~KYGaw*5AztOL^SL}Bp)+|PFz}KTI7fQ! z=T{IS_8bCA4!3HKYVg;5p75~~CST-KRF|PWy@)-eLp@1LF~Hk-@9hby@~DK)&dvk? z1P+u9_=Woo_(VrXKkzE9F!pK6Glf*|j2qLeTNWWh6y7?v`1;}_ve;clZEtrP9pA#9 zr46trI(tl~Dp&cW4CdtI3?_tzb|#mV4P4k47IxM74pn*i1FvpoedYj2-t&Y(YZ%EV zTP#q|y)+;uf?7M$XPBB8!YjG^;jpmt+u~I!K?hg|Ey?LfNJuGDhKmBu1K_HLfZKJ^ z(a#*T10;@Ww39_?6jy8)jR1f=3##3*Ta&SFIPaP*FLcf;FZcK9Tyn`JOO`Cj$+;wX zu%lzKI?H)xL&H^|xpV91etY$%2kiiB(~Z3~cTkWqEMQA-A^?aUdkhWl&}wV9FTc`1 zUa>l55bVEW&xF@OZ0%P&q@tiIumGiR=v z;q8noReUr%G&7*8t@lo{Bfe`Yy>wS|Y{}?Iq8Wf^1MX62IgCF!a3EOiZU$rnVD;*Y zQ_j9IJ-0p6*%rWVwDrBcih@QYQrK=an-Q6vi0fd4F z7e8`wj@p|D04fXsSpud?X@FD;5#PdtzLT{qfG00*Vk+&NGCwyD0#KU)It+MZ@T~Jp zGa%Qh7U74!t#k{I(V`MMHNgWWfc)5eCbi~>;CzJuTr52R4Z6={0AXPO0GR;?dLkYfOr zy$2vE07^MP3;wB}%9pc%X1YfT9(Z{D9l*u|)x!10AD*SeZQB3AWs zfGyQxb)KGk>Vz!5(zu10Th>C0qPt8a`;~O z%&&N6A!3gPSpX=)6g#3BGv1rVJJEz!ZP9`d345>c6as+BH2$R00^mmjmiW+s)q`cO zu72AT#!xpu@5x3#OW1R-%mNpp}Sf>vQf{uU}CFE1kEnv-W;{rr?b zffxlVmC2wN5`1V6QU$$nBHm=&3yDAqR_$T~RMXy@5F=|ys#U>{9#F8TbG=w}v zEjx5w>(=$_)7Qg~y7aofzSgaMb#+FAp|92Ox_>;#pHoznLZA>pZ`AuRdV7GwKUn~b zkmrf;1OG$t;88fUJf+Z(B1Ctwx(P`o626rCZonO>nE@P?yF%W;gO&N)&!6gV0QMlq z{V~iNJwfDZ9|87?@BVW@%s&LM4*eAb8{zt~VcbK4@^`WW@c&(a(;qSPw **Beta:** The exporter is still in beta. The pack format may change in a future version, so you +> might need to re-export this pack after updating the plugin or the mod. + +This archive contains two ready-to-use packs that belong together: + +- `datapack.zip`: the server-side entity profile (gameplay data) +- `resourcepack.zip`: the model, texture and render profile (visuals) + +Profile ID: `{{serverProfileId}}` + +## Requirements + +You need the **Easy Model Entities** mod installed for **{{mcVersion}}** (Forge or Fabric). +Without the mod these files do nothing. Get the mod from its distribution page (CurseForge / +Modrinth) and install it like any other mod before using this pack. + +## Installation + +You do not need to unpack anything — just move the two ZIP files into the right folders. + +### 1. Resource Pack (visuals, client side) + +Move `resourcepack.zip` into your Minecraft resourcepacks folder: + +- Windows: `%appdata%\.minecraft\resourcepacks\` +- Linux: `~/.minecraft/resourcepacks/` +- macOS: `~/Library/Application Support/minecraft/resourcepacks/` + +Then start Minecraft and enable the pack under **Options > Resource Packs**. + +### 2. Data Pack (gameplay, world / server side) + +Move `datapack.zip` into your world's `datapacks` folder: + +- Single player: `.minecraft/saves//datapacks/` +- Dedicated server: `/world/datapacks/` + +Load the world (or run `/reload` in game). On a dedicated server the Easy Model Entities mod must be +installed on the server as well. + +> Install **both** packs. The data pack defines the entity, the resource pack provides its model and +> texture. diff --git a/plugins/easy_model_entities/src/tests/exportDialog.test.js b/plugins/easy_model_entities/src/tests/exportDialog.test.js new file mode 100644 index 00000000..3ff9c9d5 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/exportDialog.test.js @@ -0,0 +1,184 @@ +const { + settingsToForm, + formToSettings, + presetFormValues, + resolveExportSettings, + presetOptions +} = require('../ui/exportDialog'); +const {fixtureSettings} = require('./fixtureData'); + +const MODEL_DIMS = { + width: 1, + height: 2, + eyeHeight: 1.8, + visibleBoundsWidth: 1.1, + visibleBoundsHeight: 2.2, + visibleBoundsOffset: [0.0, 1, 0.0] +}; + +describe('exportDialog mapping', () => { + test('settings survive a form round-trip', () => { + const settings = fixtureSettings(); + const restored = formToSettings(settingsToForm(settings), settings); + expect(restored).toEqual(settings); + }); + + test('form trims namespace and profile id', () => { + const settings = fixtureSettings(); + const form = settingsToForm(settings); + form.namespace = ' example '; + form.profileId = ' lizard '; + const restored = formToSettings(form, settings); + expect(restored.namespace).toBe('example'); + expect(restored.profileId).toBe('lizard'); + }); +}); + +describe('presetOptions', () => { + test('without experimental, only stable entity presets are shown', () => { + const options = presetOptions('entity', false); + expect(Object.keys(options)).toEqual([ + 'static', 'statue', 'humanoid_still', 'humanoid_wandering', + 'quadruped_still', 'quadruped_wandering' + ]); + expect(options.custom).toBeUndefined(); + expect(options.floating_still).toBeUndefined(); + }); + + test('experimental reveals custom and every selectable preset, no WIP mark', + () => { + const options = presetOptions('entity', true); + expect(Object.keys(options)[0]).toBe('custom'); + expect(Object.keys(options)).toHaveLength(18); + expect(options.floating_still).toBeDefined(); + expect(options.floating_still).not.toMatch(/WIP/); + }); + + test('ensure keeps an already-selected experimental preset visible', () => { + const options = presetOptions('entity', false, 'cuboid_still'); + expect(options.cuboid_still).toBeDefined(); + expect(options.custom).toBeUndefined(); + }); + + test('block entity hides ticking unless experimental is on', () => { + expect(Object.keys(presetOptions('block_entity', false))).toEqual( + ['static', 'animated', 'animated_randomly']); + expect(Object.keys(presetOptions('block_entity', true))).toEqual( + ['static', 'ticking', 'animated', 'animated_randomly']); + }); +}); + +describe('presetFormValues', () => { + test('returns advanced fields with model dimensions, no identity fields', + () => { + const values = presetFormValues('statue', 'entity', MODEL_DIMS); + expect(values.bodyType).toBe('static'); + expect(values.animationMode).toBe('none'); + expect(values.width).toBe(1); + expect(values.height).toBe(2); + expect(values.visibleBoundsHeight).toBe(2.2); + expect(values.namespace).toBeUndefined(); + expect(values.profileId).toBeUndefined(); + expect(values.targetVersion).toBeUndefined(); + expect(values.version).toBeUndefined(); + }); +}); + +describe('resolveExportSettings', () => { + const base = fixtureSettings(); + + test( + 'built-in preset derives from template and model, ignoring hidden fields', + () => { + const form = { + preset: 'statue', + namespace: 'example.org', + profileId: 'statue', + version: 'v2', + targetVersion: '1.20.1', + customize: false, + width: 999, + bodyType: 'biped' + }; + const settings = resolveExportSettings(form, base, MODEL_DIMS); + expect(settings.presetType).toBe('statue'); + expect(settings.host.bodyType).toBe('static'); + expect(settings.animation.mode).toBe('none'); + expect(settings.dimensions.width).toBe(1); + expect(settings.dimensions.height).toBe(2); + expect(settings.namespace).toBe('example.org'); + expect(settings.profileId).toBe('statue'); + expect(settings.version).toBe('v2'); + }); + + test('custom preset keeps the stored base settings', () => { + const form = { + preset: 'custom', + namespace: 'example.org', + profileId: 'lizard', + targetVersion: '1.20.1', + customize: false + }; + const settings = resolveExportSettings(form, base, MODEL_DIMS); + expect(settings.presetType).toBe('custom'); + expect(settings.dimensions).toEqual(base.dimensions); + expect(settings.host).toEqual(base.host); + }); + + test('customize overlays advanced form fields', () => { + const form = settingsToForm(fixtureSettings()); + form.preset = 'quadruped_wandering'; + form.customize = true; + form.width = 3.5; + form.maxHealth = 42; + const settings = resolveExportSettings(form, base, MODEL_DIMS); + expect(settings.dimensions.width).toBe(3.5); + expect(settings.attributes.maxHealth).toBe(42); + }); + + test('standalone ZIP export maps to the packs target', () => { + const form = { + exportType: 'packs', + modelType: 'entity', + preset: 'statue', + namespace: 'example', + profileId: 'statue', + targetVersion: '1.20.1' + }; + const settings = resolveExportSettings(form, base, MODEL_DIMS); + expect(settings.modelType).toBe('entity'); + expect(settings.exportTarget).toBe('packs'); + expect(settings.modelOnly).toBe(false); + }); + + test('block entity export uses the block preset and model type', () => { + const form = { + exportType: 'mod_project', + modelType: 'block_entity', + blockPreset: 'animated_randomly', + namespace: 'example', + profileId: 'shrine', + targetVersion: '1.20.1' + }; + const settings = resolveExportSettings(form, base, MODEL_DIMS); + expect(settings.modelType).toBe('block_entity'); + expect(settings.presetType).toBe('animated_randomly'); + expect(settings.exportTarget).toBe('mod_project'); + }); + + test('model-only export hides the data pack and forces mod project', () => { + const form = { + exportType: 'model_only', + modelType: 'block_entity', + preset: 'cuboid_still', + namespace: 'example', + profileId: 'chestling', + targetVersion: '1.20.1' + }; + const settings = resolveExportSettings(form, base, MODEL_DIMS); + expect(settings.modelOnly).toBe(true); + expect(settings.modelType).toBe('entity'); + expect(settings.presetType).toBe('cuboid_still'); + expect(settings.exportTarget).toBe('mod_project'); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/exporter.test.js b/plugins/easy_model_entities/src/tests/exporter.test.js new file mode 100644 index 00000000..0f800a6b --- /dev/null +++ b/plugins/easy_model_entities/src/tests/exporter.test.js @@ -0,0 +1,136 @@ +const {buildPackBundle, buildModProjectFiles} = require('../builders/exporter'); +const {applyTemplate} = require('../model/templates'); +const {fixtureSettings, fixtureExportOptions} = require('./fixtureData'); + +function fileByPath(files, filePath) { + return files.find((file) => file.path === filePath); +} + +const ENTITY_PROFILE_PATH = + 'data/example/easy_model_entities/profiles/entity/lizard.json'; + +describe('buildPackBundle', () => { + const bundle = buildPackBundle(fixtureSettings(), fixtureExportOptions()); + + test('wraps a README plus datapack and resourcepack file lists', () => { + expect(bundle.readme.path).toBe('README.md'); + expect(bundle.datapack.map((f) => f.path).sort()).toEqual([ + ENTITY_PROFILE_PATH, + 'pack.mcmeta' + ]); + expect(bundle.resourcepack.map((f) => f.path).sort()).toEqual([ + 'assets/example/easy_model_entities/models/lizard.bbmodel', + 'assets/example/easy_model_entities/render_profiles/lizard.json', + 'assets/example/textures/entity/lizard.png', + 'pack.mcmeta' + ].sort()); + }); + + test('README mentions the mod, Java Edition and both packs', () => { + const readme = bundle.readme.content; + expect(readme).toContain('Easy Model Entities'); + expect(readme).toContain('Minecraft: Java Edition'); + expect(readme).toContain('resourcepacks'); + expect(readme).toContain('datapacks'); + expect(readme).toContain('example:entity/lizard'); + }); + + test( + 'profiles carry model_type, preset_type, render link, no obsolete fields', + () => { + const server = JSON.parse(fileByPath(bundle.datapack, + ENTITY_PROFILE_PATH).content); + const render = JSON.parse(fileByPath(bundle.resourcepack, + 'assets/example/easy_model_entities/render_profiles/lizard.json') + .content); + expect(server.model_type).toBe('entity'); + expect(server.preset_type).toBe('quadruped_wandering'); + expect(server.schema_version).toBe('0.1.0'); + expect(server.client.render_profile).toBe('example:lizard'); + expect(render.preset_type).toBe('quadruped_wandering'); + expect(server.host).toBeUndefined(); + expect(server.pack_pair).toBeUndefined(); + expect(server.id).toBeUndefined(); + expect(render.pack_pair).toBeUndefined(); + }); + + test('pack.mcmeta carries a pack format without pair metadata', () => { + const meta = JSON.parse(fileByPath(bundle.datapack, 'pack.mcmeta').content); + expect(meta.pack.pack_format).toBe(15); + expect(meta.easy_model_entities).toBeUndefined(); + }); + + test('writes model and texture bytes unchanged as binary entries', () => { + const opts = fixtureExportOptions(); + const model = fileByPath(bundle.resourcepack, + 'assets/example/easy_model_entities/models/lizard.bbmodel'); + const texture = fileByPath(bundle.resourcepack, + 'assets/example/textures/entity/lizard.png'); + expect(model.binary).toBe(true); + expect(model.content).toBe(opts.modelBytes); + expect(texture.binary).toBe(true); + expect(texture.content).toBe(opts.textureBytes); + }); +}); + +describe('buildModProjectFiles', () => { + test('emits data and assets paths without pack.mcmeta', () => { + const {files} = buildModProjectFiles(fixtureSettings(), + fixtureExportOptions()); + expect(files.map((f) => f.path)).toEqual([ + ENTITY_PROFILE_PATH, + 'assets/example/easy_model_entities/render_profiles/lizard.json', + 'assets/example/easy_model_entities/models/lizard.bbmodel', + 'assets/example/textures/entity/lizard.png' + ]); + expect(files.some((f) => f.path.endsWith('pack.mcmeta'))).toBe(false); + }); + + test('mod project profile JSON equals the bundle profile JSON', () => { + const mod = buildModProjectFiles(fixtureSettings(), fixtureExportOptions()); + const bundle = buildPackBundle(fixtureSettings(), fixtureExportOptions()); + const modProfile = fileByPath(mod.files, ENTITY_PROFILE_PATH).content; + const bundleProfile = fileByPath(bundle.datapack, ENTITY_PROFILE_PATH) + .content; + expect(modProfile).toBe(bundleProfile); + }); +}); + +describe('model-only export (mod integration)', () => { + function modelOnlySettings() { + const settings = applyTemplate('cuboid_still'); + settings.namespace = 'example'; + settings.profileId = 'chestling'; + settings.modelOnly = true; + return settings; + } + + test('writes only render profile, model and texture (no server profile)', + () => { + const {files, serverProfile} = buildModProjectFiles(modelOnlySettings(), + fixtureExportOptions()); + expect(serverProfile).toBeNull(); + expect(files.map((f) => f.path)).toEqual([ + 'assets/example/easy_model_entities/render_profiles/chestling.json', + 'assets/example/easy_model_entities/models/chestling.bbmodel', + 'assets/example/textures/entity/chestling.png' + ]); + expect(files.some((f) => f.path.includes('/profiles/'))).toBe(false); + }); +}); + +describe('block entity export uses the block_entity subfolder', () => { + function blockSettings() { + const settings = applyTemplate('animated_randomly', 'block_entity'); + settings.namespace = 'example'; + settings.profileId = 'shrine'; + return settings; + } + + test('server profile lands under profiles/block_entity', () => { + const {files} = buildModProjectFiles(blockSettings(), + fixtureExportOptions()); + expect(files.map((f) => f.path)).toContain( + 'data/example/easy_model_entities/profiles/block_entity/shrine.json'); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/fixtureData.js b/plugins/easy_model_entities/src/tests/fixtureData.js new file mode 100644 index 00000000..ce0bbd50 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/fixtureData.js @@ -0,0 +1,45 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {applyTemplate} = require('../model/templates'); + +const FIXTURE_MODEL_BYTES = '{"meta":{"format_version":"4.5"},"name":"lizard"}'; +const FIXTURE_TEXTURE_BYTES = Uint8Array.from( + [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); + +function fixtureSettings() { + const settings = applyTemplate('quadruped_wandering'); + settings.namespace = 'example'; + settings.profileId = 'lizard'; + return settings; +} + +function fixtureExportOptions() { + return { + modelBytes: FIXTURE_MODEL_BYTES, + textureBytes: FIXTURE_TEXTURE_BYTES + }; +} + +module.exports = { + FIXTURE_MODEL_BYTES, + FIXTURE_TEXTURE_BYTES, + fixtureSettings, + fixtureExportOptions +}; diff --git a/plugins/easy_model_entities/src/tests/mdTransform.js b/plugins/easy_model_entities/src/tests/mdTransform.js new file mode 100644 index 00000000..23917446 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/mdTransform.js @@ -0,0 +1,5 @@ +module.exports = { + process(sourceText) { + return {code: `module.exports = ${JSON.stringify(sourceText)};`}; + } +}; diff --git a/plugins/easy_model_entities/src/tests/modelDimensions.test.js b/plugins/easy_model_entities/src/tests/modelDimensions.test.js new file mode 100644 index 00000000..31188d32 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/modelDimensions.test.js @@ -0,0 +1,81 @@ +const {ModelDimensions} = require('../model/ModelDimensions'); + +describe('deriveDimensions', () => { + test('derives blocks from pixel bounds and uses the widest horizontal extent', + () => { + const bounds = {minX: 0, minY: 0, minZ: 0, maxX: 16, maxY: 32, maxZ: 8}; + const dims = ModelDimensions.deriveDimensions(bounds, 'biped'); + expect(dims.width).toBe(1); // 16px / 16 + expect(dims.height).toBe(2); // 32px / 16 + expect(dims.eyeHeight).toBe(1.8); // 2 * 0.9 (biped) + expect(dims.visibleBoundsOffset).toEqual([0.0, 1, 0.0]); + }); + + test('eye height factor depends on body type', () => { + const bounds = {minX: 0, minY: 0, minZ: 0, maxX: 16, maxY: 16, maxZ: 16}; + expect(ModelDimensions.deriveDimensions(bounds, 'biped').eyeHeight).toBe( + 0.9); + expect( + ModelDimensions.deriveDimensions(bounds, 'quadruped').eyeHeight).toBe( + 0.8); + expect(ModelDimensions.deriveDimensions(bounds, 'static').eyeHeight).toBe( + 0.85); + }); + + test('falls back for missing, empty or degenerate bounds', () => { + expect(ModelDimensions.deriveDimensions(null, 'biped')).toEqual( + ModelDimensions.FALLBACK); + expect(ModelDimensions.deriveDimensions( + {minX: NaN, minY: 0, minZ: 0, maxX: 1, maxY: 1, maxZ: 1}, + 'biped')).toEqual(ModelDimensions.FALLBACK); + expect(ModelDimensions.deriveDimensions( + {minX: 5, minY: 5, minZ: 5, maxX: 5, maxY: 5, maxZ: 5}, + 'biped')).toEqual(ModelDimensions.FALLBACK); + }); + + test('does not share the fallback offset array instance', () => { + const a = ModelDimensions.deriveDimensions(null, 'biped'); + a.visibleBoundsOffset[0] = 99; + expect(ModelDimensions.FALLBACK.visibleBoundsOffset[0]).toBe(0.0); + }); +}); + +describe('applyModelDimensions', () => { + const dims = { + width: 1, + height: 2, + eyeHeight: 1.8, + visibleBoundsWidth: 1.1, + visibleBoundsHeight: 2.2, + visibleBoundsOffset: [0.0, 1, 0.0] + }; + + function settings() { + return { + dimensions: {width: 0, height: 0, eyeHeight: 0}, + rendering: { + visibleBoundsWidth: 0, + visibleBoundsHeight: 0, + visibleBoundsOffset: [0, 0, 0] + } + }; + } + + test('overlays dimensions and visible bounds', () => { + const result = ModelDimensions.applyModelDimensions(settings(), dims); + expect(result.dimensions).toEqual({width: 1, height: 2, eyeHeight: 1.8}); + expect(result.rendering.visibleBoundsWidth).toBe(1.1); + expect(result.rendering.visibleBoundsOffset).toEqual([0.0, 1, 0.0]); + }); + + test('does not share the offset array with the source', () => { + const result = ModelDimensions.applyModelDimensions(settings(), dims); + result.rendering.visibleBoundsOffset[0] = 99; + expect(dims.visibleBoundsOffset[0]).toBe(0.0); + }); + + test('returns settings unchanged when no dimensions are given', () => { + const input = settings(); + expect(ModelDimensions.applyModelDimensions(input, null)).toBe(input); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/presetDetector.test.js b/plugins/easy_model_entities/src/tests/presetDetector.test.js new file mode 100644 index 00000000..99713116 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/presetDetector.test.js @@ -0,0 +1,70 @@ +const {PresetDetector} = require('../model/PresetDetector'); + +function stats(boneNames) { + return {boneNames}; +} + +describe('PresetDetector.detectPresetType', () => { + test('detects a quadruped from front/back legs', () => { + expect(PresetDetector.detectPresetType( + stats(['root', 'body', 'head', 'front_left_leg', 'back_right_leg']))) + .toBe('quadruped_still'); + }); + + test('detects four legs without front/back naming', () => { + expect(PresetDetector.detectPresetType( + stats(['leg1', 'leg2', 'leg3', 'leg4']))).toBe('quadruped_still'); + }); + + test('detects a humanoid from arms', () => { + expect(PresetDetector.detectPresetType( + stats(['root', 'head', 'body', 'left_arm', 'right_arm', 'left_leg']))) + .toBe('humanoid_still'); + }); + + test('detects winged from wing bones', () => { + expect(PresetDetector.detectPresetType( + stats(['body', 'left_wing', 'right_wing']))).toBe('winged_still'); + }); + + test('detects winged humanoid when wings and arms are present', () => { + expect(PresetDetector.detectPresetType( + stats(['body', 'left_arm', 'right_arm', 'left_wing', 'right_wing']))) + .toBe('winged_humanoid_still'); + }); + + test('detects aquatic from fins', () => { + expect(PresetDetector.detectPresetType( + stats(['body', 'dorsal_fin', 'tail_fin']))).toBe('aquatic_still'); + }); + + test('detects arthropod from six or more legs', () => { + expect(PresetDetector.detectPresetType( + stats(['leg1', 'leg2', 'leg3', 'leg4', 'leg5', 'leg6']))).toBe( + 'arthropod_still'); + }); + + test('matches bone names case-insensitively', () => { + expect(PresetDetector.detectPresetType(stats(['Left_Arm', 'RIGHT_ARM']))) + .toBe('humanoid_still'); + }); + + test('uses cube-like bounds to suggest cuboid when no limbs', () => { + const bounds = {minX: 0, minY: 0, minZ: 0, maxX: 16, maxY: 16, maxZ: 16}; + expect(PresetDetector.detectPresetType(stats(['root', 'body']), bounds)) + .toBe('cuboid_still'); + }); + + test('falls back to statue without limbs or usable bounds', () => { + expect(PresetDetector.detectPresetType(stats(['root', 'body', 'head']))) + .toBe('statue'); + expect(PresetDetector.detectPresetType(stats([]))).toBe('statue'); + expect(PresetDetector.detectPresetType(stats(undefined))).toBe('statue'); + }); + + test('detect returns a human readable reason', () => { + const result = PresetDetector.detect(stats(['front_left_leg'])); + expect(result.presetType).toBe('quadruped_still'); + expect(typeof result.reason).toBe('string'); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/readme.test.js b/plugins/easy_model_entities/src/tests/readme.test.js new file mode 100644 index 00000000..ad6ecde8 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/readme.test.js @@ -0,0 +1,29 @@ +const {buildReadme} = require('../builders/readme'); +const {fixtureSettings} = require('./fixtureData'); + +describe('buildReadme', () => { + const readme = buildReadme(fixtureSettings()); + + test('states the mod requirement and Java Edition target', () => { + expect(readme).toContain('Easy Model Entities'); + expect(readme).toContain('Minecraft: Java Edition'); + expect(readme).toMatch(/mod .*installed|installed .*mod/i); + }); + + test('explains where to install both packs as ready-to-use ZIPs', () => { + expect(readme).toContain('resourcepacks'); + expect(readme).toContain('saves//datapacks'); + expect(readme).toContain('resourcepack.zip'); + expect(readme).toContain('datapack.zip'); + expect(readme).toContain('Resource Pack'); + expect(readme).toContain('Data Pack'); + }); + + test('references the profile id', () => { + expect(readme).toContain('example:lizard'); + }); + + test('names the target Minecraft version', () => { + expect(readme).toContain('Minecraft: Java Edition 1.20.1'); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/resourceLocation.test.js b/plugins/easy_model_entities/src/tests/resourceLocation.test.js new file mode 100644 index 00000000..3bb7201e --- /dev/null +++ b/plugins/easy_model_entities/src/tests/resourceLocation.test.js @@ -0,0 +1,57 @@ +const {ResourceLocation} = require('../utils/ResourceLocation'); + +describe('resourceLocation', () => { + test('accepts valid namespaces', () => { + expect(ResourceLocation.isValidNamespace('example')).toBe(true); + expect(ResourceLocation.isValidNamespace('easy_model_entities')).toBe(true); + expect(ResourceLocation.isValidNamespace('mod.id-1')).toBe(true); + }); + + test('rejects invalid namespaces', () => { + expect(ResourceLocation.isValidNamespace('Example')).toBe(false); + expect(ResourceLocation.isValidNamespace('with space')).toBe(false); + expect(ResourceLocation.isValidNamespace('')).toBe(false); + expect(ResourceLocation.isValidNamespace('a/b')).toBe(false); + }); + + test('paths allow slashes but reject traversal', () => { + expect(ResourceLocation.isValidPath('models/lizard')).toBe(true); + expect(ResourceLocation.isValidPath('lizard')).toBe(true); + expect(ResourceLocation.isValidPath('../escape')).toBe(false); + expect(ResourceLocation.isValidPath('Upper')).toBe(false); + }); + + test('validates and parses full resource locations', () => { + expect(ResourceLocation.isValidResourceLocation('example:lizard')).toBe( + true); + expect(ResourceLocation.isValidResourceLocation( + 'easy_model_entities:ground_entity')).toBe(true); + expect(ResourceLocation.isValidResourceLocation('no_colon')).toBe(false); + expect(ResourceLocation.isValidResourceLocation('a:b:c')).toBe(false); + expect(ResourceLocation.parseResourceLocation('example:lizard')).toEqual( + {namespace: 'example', path: 'lizard'}); + expect(ResourceLocation.parseResourceLocation('bad')).toBeNull(); + }); +}); + +describe('sanitizeProfileId', () => { + test('strips extension, lowercases and replaces invalid characters', () => { + expect(ResourceLocation.sanitizeProfileId('My Model.bbmodel')).toBe( + 'my_model'); + expect(ResourceLocation.sanitizeProfileId('Stone Turtle')).toBe( + 'stone_turtle'); + expect(ResourceLocation.sanitizeProfileId('Lizard')).toBe('lizard'); + }); + + test('collapses repeats and trims separators, result is a valid path', () => { + const id = ResourceLocation.sanitizeProfileId(' --Cool!!Robot-- '); + expect(id).toBe('cool_robot'); + expect(ResourceLocation.isValidPath(id)).toBe(true); + }); + + test('falls back to "entity" for empty or unusable names', () => { + expect(ResourceLocation.sanitizeProfileId('')).toBe('entity'); + expect(ResourceLocation.sanitizeProfileId('!!!')).toBe('entity'); + expect(ResourceLocation.sanitizeProfileId(undefined)).toBe('entity'); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/schemaConsistency.test.js b/plugins/easy_model_entities/src/tests/schemaConsistency.test.js new file mode 100644 index 00000000..0b6bae98 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/schemaConsistency.test.js @@ -0,0 +1,117 @@ +const {applyTemplate, deepMerge, SELECTABLE_PRESET_TYPES} = require( + '../model/templates'); +const {buildServerProfile} = require('../builders/serverProfile'); +const {buildRenderProfile} = require('../builders/renderProfile'); + +function withId(settings) { + settings.namespace = 'example'; + settings.profileId = 'lizard'; + return settings; +} + +// If the plugin's preset defaults exactly mirror the mod's, an unmodified +// preset must reduce to just the model_type, preset_type and the required +// client.render_profile link (the mod fills in every other value from +// preset_type), proving the two stay in sync. +describe('preset defaults mirror the mod (minimal output)', () => { + describe.each(SELECTABLE_PRESET_TYPES)('%s', (preset) => { + const settings = withId(applyTemplate(preset)); + + test('server profile reduces to identity + render link', () => { + expect(buildServerProfile(settings)).toEqual({ + schema_version: '0.1.0', + model_type: 'entity', + preset_type: preset, + client: {render_profile: 'example:lizard'} + }); + }); + + test('render profile reduces to schema_version + preset_type', () => { + expect(buildRenderProfile(settings)).toEqual({ + schema_version: '0.1.0', + preset_type: preset + }); + }); + }); +}); + +describe('custom preset writes the mod-required entity and dimensions', () => { + const settings = withId(applyTemplate('custom')); + + test('server profile includes the full entity block and dimensions', () => { + const profile = buildServerProfile(settings); + expect(profile.model_type).toBe('entity'); + expect(profile.preset_type).toBe('custom'); + expect(profile.entity).toEqual({ + type: 'easy_model_entities:static_entity', + movement_type: 'static', + body_type: 'static' + }); + expect(profile.dimensions).toEqual( + {width: 0.6, height: 1.8, eye_height: 1.62}); + }); + + test('render profile includes the required body_type', () => { + expect(buildRenderProfile(settings).body_type).toBe('static'); + }); +}); + +describe('block entity profiles', () => { + function blockSettings(preset) { + const settings = applyTemplate(preset, 'block_entity'); + settings.namespace = 'example'; + settings.profileId = 'shrine'; + return settings; + } + + test('server profile carries model_type, render link and no entity behavior', + () => { + const profile = buildServerProfile(blockSettings('animated_randomly')); + expect(profile).toEqual({ + schema_version: '0.1.0', + model_type: 'block_entity', + preset_type: 'animated_randomly', + client: {render_profile: 'example:shrine'} + }); + expect(profile.entity).toBeUndefined(); + expect(profile.movement).toBeUndefined(); + expect(profile.behavior).toBeUndefined(); + expect(profile.attributes).toBeUndefined(); + }); + + test('render profile uses the static render preset with mapped animation', + () => { + const render = buildRenderProfile(blockSettings('animated_randomly')); + expect(render.preset_type).toBe('static'); + expect(render.body_type).toBeUndefined(); + expect(render.animation.mode).toBe('random_idle'); + }); + + test('static block entity emits no animation override', () => { + const render = buildRenderProfile(blockSettings('static')); + expect(render.preset_type).toBe('static'); + expect(render.animation).toBeUndefined(); + }); +}); + +describe('customized settings emit only the deviating values', () => { + const settings = deepMerge(withId(applyTemplate('quadruped_wandering')), { + version: 'demo-stone-turtle-v4', + dimensions: {width: 1.2, height: 0.85, eyeHeight: 0.6}, + movement: {speed: 0.06, stepHeight: 0.4, gravity: true}, + attributes: {maxHealth: 12, movementSpeed: 0.06, followRange: 12} + }); + + test('server profile contains the stone turtle overrides only', () => { + expect(buildServerProfile(settings)).toEqual({ + schema_version: '0.1.0', + model_type: 'entity', + preset_type: 'quadruped_wandering', + version: 'demo-stone-turtle-v4', + client: {render_profile: 'example:lizard'}, + dimensions: {width: 1.2, height: 0.85}, + movement: {speed: 0.06, step_height: 0.4}, + attributes: {max_health: 12, follow_range: 12} + }); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/templates.test.js b/plugins/easy_model_entities/src/tests/templates.test.js new file mode 100644 index 00000000..a8528160 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/templates.test.js @@ -0,0 +1,93 @@ +const { + getDefaults, + applyTemplate, + DEFAULT_PRESET, + PRESET_TYPES, + SELECTABLE_PRESET_TYPES, + deepMerge +} = require('../model/templates'); +const {presetDefaults} = require('../model/presetTypes'); + +describe('templates', () => { + test('exposes the 18 mod preset types', () => { + expect(PRESET_TYPES).toHaveLength(18); + expect(PRESET_TYPES).toContain('custom'); + expect(PRESET_TYPES).toContain('quadruped_wandering'); + expect(PRESET_TYPES).toContain('floating_still'); + }); + + test('selectable presets exclude custom', () => { + expect(SELECTABLE_PRESET_TYPES).not.toContain('custom'); + expect(SELECTABLE_PRESET_TYPES).toHaveLength(17); + }); + + test('getDefaults carries identity fields and the default preset', () => { + const d = getDefaults(); + expect(d.namespace).toBe('example_org'); + expect(d.profileId).toBe('entity'); + expect(d.targetVersion).toBe('1.20.1'); + expect(d.presetType).toBe(DEFAULT_PRESET); + }); + + test('getDefaults returns an independent copy', () => { + const a = getDefaults(); + a.dimensions.width = 99; + expect(getDefaults().dimensions.width).not.toBe(99); + }); + + test('applyTemplate merges preset defaults onto the identity fields', () => { + const s = applyTemplate('humanoid_wandering'); + expect(s.namespace).toBe('example_org'); + expect(s.presetType).toBe('humanoid_wandering'); + expect(s.host).toEqual({ + entityType: 'easy_model_entities:ground_entity', + movementType: 'ground', + bodyType: 'biped' + }); + expect(s.behavior.mode).toBe('ambient'); + expect(s.animation.mode).toBe('automatic'); + }); + + test('still presets are static hosts that idle', () => { + const s = applyTemplate('quadruped_still'); + expect(s.host.movementType).toBe('static'); + expect(s.host.entityType).toBe('easy_model_entities:static_entity'); + expect(s.behavior.mode).toBe('idle_only'); + expect(s.movement.speed).toBe(0); + }); + + test('statue disables movement and animation', () => { + const s = applyTemplate('statue'); + expect(s.host.bodyType).toBe('static'); + expect(s.behavior.mode).toBe('static'); + expect(s.animation.mode).toBe('none'); + }); +}); + +describe('presetDefaults', () => { + test.each(SELECTABLE_PRESET_TYPES)( + 'produces a complete settings object for %s', + (preset) => { + const d = presetDefaults(preset); + expect(d.presetType).toBe(preset); + expect(d.host.bodyType).toBeTruthy(); + expect(Number.isFinite(d.dimensions.width)).toBe(true); + expect(Number.isFinite(d.dimensions.height)).toBe(true); + expect(d.dimensions.eyeHeight).toBeLessThanOrEqual(d.dimensions.height); + }); + + test('moving presets enable ground movement and ambient strolling', () => { + const d = presetDefaults('quadruped_wandering'); + expect(d.host.movementType).toBe('ground'); + expect(d.movement.speed).toBe(0.22); + expect(d.behavior).toMatchObject( + {mode: 'ambient', lookAtPlayers: true, randomStroll: true}); + }); + + test('deepMerge overrides nested values and copies arrays', () => { + const merged = deepMerge(presetDefaults('cuboid_still'), + {rendering: {scale: 2}}); + expect(merged.rendering.scale).toBe(2); + expect(merged.rendering.visibleBoundsOffset).toEqual([0, 0.5, 0]); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/validation.test.js b/plugins/easy_model_entities/src/tests/validation.test.js new file mode 100644 index 00000000..f24b99d3 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/validation.test.js @@ -0,0 +1,139 @@ +const {Validator} = require('../model/Validator'); +const {fixtureSettings} = require('./fixtureData'); + +function codes(list) { + return list.map((entry) => entry.code); +} + +const QUADRUPED_BONES = ['root', 'body', 'head', 'front_left_leg', + 'front_right_leg', 'back_left_leg', 'back_right_leg']; + +describe('validateSettings', () => { + test('valid default settings produce no errors', () => { + const result = Validator.validateSettings(fixtureSettings(), { + hasModel: true, + hasTexture: true, + boneNames: QUADRUPED_BONES + }); + expect(result.valid).toBe(true); + expect(result.errors).toHaveLength(0); + }); + + test('rejects invalid namespace and profile id', () => { + const s = fixtureSettings(); + s.namespace = 'Bad NS'; + s.profileId = '../escape'; + const result = Validator.validateSettings(s, + {hasModel: true, hasTexture: true}); + expect(result.valid).toBe(false); + expect(codes(result.errors)).toEqual( + expect.arrayContaining(['INVALID_NAMESPACE', 'INVALID_PROFILE_ID'])); + }); + + test('rejects an unknown preset type', () => { + const s = fixtureSettings(); + s.presetType = 'bogus'; + const result = Validator.validateSettings(s, + {hasModel: true, hasTexture: true}); + expect(codes(result.errors)).toContain('INVALID_PRESET_TYPE'); + }); + + test('rejects non-positive dimensions and eye height above height', () => { + const s = fixtureSettings(); + s.dimensions = {width: 0, height: -1, eyeHeight: 5}; + const result = Validator.validateSettings(s, + {hasModel: true, hasTexture: true}); + expect(codes(result.errors).filter( + (c) => c === 'INVALID_DIMENSIONS').length).toBeGreaterThanOrEqual(2); + }); + + test('rejects NaN/Infinity dimensions', () => { + const s = fixtureSettings(); + s.dimensions = {width: NaN, height: Infinity, eyeHeight: 0.5}; + const result = Validator.validateSettings(s, + {hasModel: true, hasTexture: true}); + expect(codes(result.errors)).toContain('INVALID_NUMERIC'); + }); + + test('flags missing model and texture', () => { + const result = Validator.validateSettings(fixtureSettings(), + {hasModel: false, hasTexture: false}); + expect(codes(result.errors)).toEqual( + expect.arrayContaining(['MISSING_MODEL', 'MISSING_TEXTURE'])); + }); + + test('warns when multiple textures are present', () => { + const result = Validator.validateSettings(fixtureSettings(), { + hasModel: true, + hasTexture: true, + textureCount: 3, + boneNames: QUADRUPED_BONES + }); + expect(result.valid).toBe(true); + expect(codes(result.warnings)).toContain('MULTIPLE_TEXTURES'); + }); + + test('warns about performance budgets without blocking', () => { + const result = Validator.validateSettings(fixtureSettings(), { + hasModel: true, + hasTexture: true, + textureWidth: 4096, + textureHeight: 4096, + modelFileSize: 5 * 1024 * 1024, + cubeCount: 1000, + boneCount: 200, + hierarchyDepth: 50, + boneNames: QUADRUPED_BONES + }); + expect(result.valid).toBe(true); + expect(codes(result.warnings)).toEqual( + expect.arrayContaining( + ['LARGE_TEXTURE', 'LARGE_MODEL', 'HIGH_CUBE_COUNT', + 'HIGH_BONE_COUNT', 'DEEP_HIERARCHY']) + ); + }); + + test('warns about missing body parts per body type', () => { + const result = Validator.validateSettings(fixtureSettings(), + {hasModel: true, hasTexture: true, boneNames: ['root', 'body']}); + expect(codes(result.warnings).filter( + (c) => c === 'MISSING_BODY_PART').length).toBeGreaterThan(0); + }); +}); + +describe('getMissingBodyParts', () => { + test('static body type requires no parts', () => { + expect(Validator.getMissingBodyParts('static', [])).toEqual([]); + }); + + test('unknown body types require no parts', () => { + expect(Validator.getMissingBodyParts('aquatic', [])).toEqual([]); + }); + + test('biped reports missing limbs case-insensitively', () => { + expect(Validator.getMissingBodyParts('biped', + ['ROOT', 'Head', 'Body'])).toEqual([ + 'left_arm', + 'right_arm', + 'left_leg', + 'right_leg' + ]); + }); +}); + +describe('validateOutputPath', () => { + test('accepts a clean relative path', () => { + expect(Validator.validateOutputPath('/root', + 'data/example/profiles/lizard.json').valid).toBe(true); + }); + + test('rejects traversal and absolute paths', () => { + expect(Validator.validateOutputPath('/root', '../../etc/passwd').code).toBe( + 'PATH_TRAVERSAL'); + expect(Validator.validateOutputPath('/root', '/etc/passwd').code).toBe( + 'ABSOLUTE_PATH'); + expect( + Validator.validateOutputPath('/root', 'C:/Windows/system32').code).toBe( + 'ABSOLUTE_PATH'); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/versionMatrix.test.js b/plugins/easy_model_entities/src/tests/versionMatrix.test.js new file mode 100644 index 00000000..bc33b57c --- /dev/null +++ b/plugins/easy_model_entities/src/tests/versionMatrix.test.js @@ -0,0 +1,29 @@ +const { + getVersions, + getEnabledVersions, + getDefaultVersionId, + getPackFormats +} = require('../model/versionMatrix'); + +describe('versionMatrix', () => { + test('default version is 1.20.1 and enabled', () => { + expect(getDefaultVersionId()).toBe('1.20.1'); + const enabled = getEnabledVersions().map((v) => v.id); + expect(enabled).toEqual(['1.20.1']); + }); + + test('exposes the full selectable version list', () => { + expect(getVersions().map((v) => v.id)).toEqual( + ['1.20.1', '1.21.1', '1.21.11', '26.1.2']); + }); + + test('1.20.1 maps to pack_format 15 / 15', () => { + expect(getPackFormats('1.20.1')).toEqual( + {dataFormat: 15, resourceFormat: 15}); + }); + + test('disabled versions return no pack formats', () => { + expect(getPackFormats('1.21.1')).toBeNull(); + expect(getPackFormats('does-not-exist')).toBeNull(); + }); +}); diff --git a/plugins/easy_model_entities/src/ui/exportDialog.js b/plugins/easy_model_entities/src/ui/exportDialog.js new file mode 100644 index 00000000..16de64cd --- /dev/null +++ b/plugins/easy_model_entities/src/ui/exportDialog.js @@ -0,0 +1,544 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {getVersions} = require('../model/versionMatrix'); +const {getDefaults, applyTemplate, deepMerge} = require('../model/templates'); +const { + SELECTABLE_PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + isStablePreset, + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY +} = require('../model/presetTypes'); +const {ModelDimensions} = require('../model/ModelDimensions'); +const {t} = require('../i18n/translations'); + +function presetLabel(id) { + return id === 'custom' ? t('eme.preset.custom') : t(`eme.preset.${id}`); +} + +function entityPresetIds(experimental) { + return experimental + ? ['custom', ...SELECTABLE_PRESET_TYPES] + : SELECTABLE_PRESET_TYPES.filter(isStablePreset); +} + +function blockPresetIds(experimental) { + return experimental + ? BLOCK_ENTITY_PRESET_TYPES.slice() + : BLOCK_ENTITY_PRESET_TYPES.filter(isStablePreset); +} + +// Experimental (WIP) presets are hidden unless the experimental setting is on. +// `ensure` keeps an already-selected preset visible even when experimental is +// off, so reopening a project that used one never breaks the dropdown. +function presetOptions(modelType, experimental, ensure) { + const ids = modelType === MODEL_TYPE_BLOCK_ENTITY + ? blockPresetIds(experimental) : entityPresetIds(experimental); + if (ensure && !ids.includes(ensure)) { + ids.push(ensure); + } + const options = {}; + ids.forEach((id) => { + options[id] = presetLabel(id); + }); + return options; +} + +function modelTypeOptions() { + return { + [MODEL_TYPE_ENTITY]: t('eme.modelType.entity'), + [MODEL_TYPE_BLOCK_ENTITY]: t('eme.modelType.block_entity') + }; +} + +function exportTypeOptions() { + return { + packs: t('eme.exportType.packs'), + mod_project: t('eme.exportType.mod_project'), + model_only: t('eme.exportType.model_only') + }; +} + +function hostEntityTypeOptions() { + return { + 'easy_model_entities:ground_entity': t('eme.entity.ground_entity'), + 'easy_model_entities:static_entity': t('eme.entity.static_entity') + }; +} + +function movementTypeOptions() { + return {ground: t('eme.movement.ground'), static: t('eme.movement.static')}; +} + +function bodyTypeOptions() { + return { + static: t('eme.body.static'), + biped: t('eme.body.biped'), + quadruped: t('eme.body.quadruped'), + aquatic: t('eme.body.aquatic'), + winged: t('eme.body.winged'), + winged_humanoid: t('eme.body.winged_humanoid'), + arthropod: t('eme.body.arthropod'), + cuboid: t('eme.body.cuboid'), + floating: t('eme.body.floating') + }; +} + +function behaviorModeOptions() { + return { + idle_only: t('eme.behavior.idle_only'), + ambient: t('eme.behavior.ambient'), + static: t('eme.behavior.static'), + external_owner: t('eme.behavior.external_owner') + }; +} + +function animationModeOptions() { + return { + automatic: t('eme.animation.automatic'), + random_idle: t('eme.animation.random_idle'), + none: t('eme.animation.none') + }; +} + +function versionOptions() { + const options = {}; + getVersions().forEach((version) => { + options[version.id] = version.enabled ? version.label + : `${version.label} (coming soon)`; + }); + return options; +} + +function settingsToForm(settings) { + return { + namespace: settings.namespace, + profileId: settings.profileId, + version: settings.version || '', + targetVersion: settings.targetVersion, + hostEntityType: settings.host.entityType, + movementType: settings.host.movementType, + bodyType: settings.host.bodyType, + width: settings.dimensions.width, + height: settings.dimensions.height, + eyeHeight: settings.dimensions.eyeHeight, + speed: settings.movement.speed, + stepHeight: settings.movement.stepHeight, + gravity: settings.movement.gravity, + behaviorMode: settings.behavior.mode, + maxHealth: settings.attributes.maxHealth, + movementSpeed: settings.attributes.movementSpeed, + followRange: settings.attributes.followRange, + scale: settings.rendering.scale, + shadowRadius: settings.rendering.shadowRadius, + visibleBoundsWidth: settings.rendering.visibleBoundsWidth, + visibleBoundsHeight: settings.rendering.visibleBoundsHeight, + visibleBoundsOffset: settings.rendering.visibleBoundsOffset.slice(), + animationMode: settings.animation.mode, + swingSpeed: settings.animation.swingSpeed, + walkSpeedMultiplier: settings.animation.walkSpeedMultiplier + }; +} + +function formToSettings(form, base) { + return { + schemaVersion: base.schemaVersion, + modelType: base.modelType, + presetType: base.presetType, + version: base.version || '', + namespace: String(form.namespace || '').trim(), + profileId: String(form.profileId || '').trim(), + targetVersion: base.targetVersion, + host: { + entityType: form.hostEntityType, + movementType: form.movementType, + bodyType: form.bodyType + }, + dimensions: { + width: Number(form.width), + height: Number(form.height), + eyeHeight: Number(form.eyeHeight) + }, + movement: { + speed: Number(form.speed), + stepHeight: Number(form.stepHeight), + gravity: !!form.gravity + }, + behavior: { + mode: form.behaviorMode, + lookAtPlayers: base.behavior ? base.behavior.lookAtPlayers : true, + randomStroll: base.behavior ? base.behavior.randomStroll : false + }, + attributes: { + maxHealth: Number(form.maxHealth), + movementSpeed: Number(form.movementSpeed), + followRange: Number(form.followRange) + }, + rendering: { + scale: Number(form.scale), + shadowRadius: Number(form.shadowRadius), + visibleBoundsWidth: Number(form.visibleBoundsWidth), + visibleBoundsHeight: Number(form.visibleBoundsHeight), + visibleBoundsOffset: (form.visibleBoundsOffset || [0, 0, 0]).map(Number) + }, + animation: { + mode: form.animationMode, + swingSpeed: Number(form.swingSpeed), + walkSpeedMultiplier: Number(form.walkSpeedMultiplier) + } + }; +} + +// Active preset depends on whether a block entity datapack is being authored. +// Model-only export still authors a render (body) preset, so it uses the entity +// preset list. +function activeModelType(form) { + return form.exportType === 'model_only' + ? MODEL_TYPE_ENTITY : (form.modelType || MODEL_TYPE_ENTITY); +} + +function activePreset(form) { + return activeModelType(form) === MODEL_TYPE_BLOCK_ENTITY + ? (form.blockPreset || 'static') : (form.preset || 'custom'); +} + +function presetFormValues(presetType, modelType, modelDimensions) { + const settings = ModelDimensions.applyModelDimensions( + applyTemplate(presetType, modelType), modelDimensions); + const form = settingsToForm(settings); + delete form.namespace; + delete form.profileId; + delete form.targetVersion; + delete form.version; + return form; +} + +function resolveExportSettings(form, base, modelDimensions) { + const exportType = form.exportType || 'packs'; + const modelOnly = exportType === 'model_only'; + const modelType = activeModelType(form); + const blockEntity = modelType === MODEL_TYPE_BLOCK_ENTITY; + const preset = activePreset(form); + + let settings; + if (!blockEntity && preset === 'custom') { + settings = deepMerge(getDefaults(), base); + } else { + settings = ModelDimensions.applyModelDimensions( + applyTemplate(preset, modelType), modelDimensions); + } + + settings.schemaVersion = base.schemaVersion || settings.schemaVersion; + settings.modelType = modelType; + settings.presetType = preset; + settings.namespace = String(form.namespace || '').trim(); + settings.profileId = String(form.profileId || '').trim(); + settings.version = String(form.version || '').trim(); + settings.targetVersion = form.targetVersion; + + if (form.customize) { + settings = formToSettings(form, settings); + } + // Remembered so a customized project reopens with its values (and target). + settings.customize = !!form.customize; + settings.exportType = exportType; + settings.exportTarget = exportType === 'packs' ? 'packs' : 'mod_project'; + settings.modelOnly = modelOnly; + return settings; +} + +function advancedField(field, showCustomize) { + return Object.assign({condition: showCustomize}, field); +} + +function buildFormConfig(settings, ui) { + const values = settingsToForm(settings); + const state = ui || {}; + const allowCustomize = !!state.showCustomization; + + const standalone = (form) => form.exportType !== 'model_only'; + const blockEntitySelected = (form) => + standalone(form) && form.modelType === MODEL_TYPE_BLOCK_ENTITY; + const customizeOn = (form) => allowCustomize && !!form.customize; + // Server-profile advanced fields only make sense for a standalone datapack. + const showServer = (form) => customizeOn(form) && standalone(form); + // Host/movement/behavior/attributes only apply to standalone entities. + const showEntity = (form) => showServer(form) && !blockEntitySelected(form); + // Render-profile advanced fields apply to every export kind. + const showRender = customizeOn; + + const experimental = !!state.experimental; + const entityValue = state.preset || 'statue'; + const blockValue = state.blockPreset || 'static'; + + const config = { + exportType: { + label: t('eme.field.exportType'), + type: 'select', + options: exportTypeOptions(), + value: state.exportType || 'packs' + }, + modelType: { + label: t('eme.field.modelType'), + type: 'select', + options: modelTypeOptions(), + value: state.modelType || MODEL_TYPE_ENTITY, + condition: standalone + }, + preset: { + label: t('eme.field.preset'), + type: 'select', + options: presetOptions(MODEL_TYPE_ENTITY, experimental, entityValue), + value: entityValue, + condition: (form) => !blockEntitySelected(form) + }, + blockPreset: { + label: t('eme.field.preset'), + type: 'select', + options: presetOptions(MODEL_TYPE_BLOCK_ENTITY, experimental, blockValue), + value: blockValue, + condition: blockEntitySelected + }, + namespace: { + label: t('eme.field.namespace'), + type: 'text', + value: values.namespace, + placeholder: 'your_mod_id' + }, + profileId: { + label: t('eme.field.profileId'), type: 'text', + value: values.profileId + }, + version: { + label: t('eme.field.version'), + type: 'text', + value: values.version, + placeholder: 'v1' + }, + targetVersion: { + label: t('eme.field.targetVersion'), + type: 'select', + options: versionOptions(), + value: values.targetVersion + } + }; + + if (allowCustomize) { + config.customize = { + label: t('eme.field.customize'), + type: 'checkbox', + value: !!state.customize + }; + } + + Object.assign(config, { + host_header: advancedField( + {type: 'info', text: `### ${t('eme.section.host')}`}, showEntity), + hostEntityType: advancedField({ + label: t('eme.field.hostEntityType'), + type: 'select', + options: hostEntityTypeOptions(), + value: values.hostEntityType + }, showEntity), + movementType: advancedField({ + label: t('eme.field.movementType'), + type: 'select', + options: movementTypeOptions(), + value: values.movementType + }, showEntity), + bodyType: advancedField({ + label: t('eme.field.bodyType'), + type: 'select', + options: bodyTypeOptions(), + value: values.bodyType + }, showEntity), + + dimensions_header: advancedField( + {type: 'info', text: `### ${t('eme.section.dimensions')}`}, + showServer), + width: advancedField( + { + label: t('eme.field.width'), type: 'number', value: values.width, + step: 0.1 + }, showServer), + height: advancedField( + { + label: t('eme.field.height'), type: 'number', value: values.height, + step: 0.1 + }, showServer), + eyeHeight: advancedField( + { + label: t('eme.field.eyeHeight'), type: 'number', + value: values.eyeHeight, step: 0.1 + }, showServer), + + movement_header: advancedField( + {type: 'info', text: `### ${t('eme.section.movement')}`}, showEntity), + speed: advancedField( + { + label: t('eme.field.speed'), type: 'number', value: values.speed, + step: 0.01 + }, showEntity), + stepHeight: advancedField( + { + label: t('eme.field.stepHeight'), type: 'number', + value: values.stepHeight, step: 0.1 + }, showEntity), + gravity: advancedField( + { + label: t('eme.field.gravity'), type: 'checkbox', + value: values.gravity + }, showEntity), + + behavior_header: advancedField( + {type: 'info', text: `### ${t('eme.section.behavior')}`}, showEntity), + behaviorMode: advancedField({ + label: t('eme.field.behaviorMode'), + type: 'select', + options: behaviorModeOptions(), + value: values.behaviorMode + }, showEntity), + + attributes_header: advancedField( + {type: 'info', text: `### ${t('eme.section.attributes')}`}, + showEntity), + maxHealth: advancedField( + { + label: t('eme.field.maxHealth'), type: 'number', + value: values.maxHealth, step: 0.5 + }, showEntity), + movementSpeed: advancedField( + { + label: t('eme.field.movementSpeed'), type: 'number', + value: values.movementSpeed, step: 0.01 + }, showEntity), + followRange: advancedField( + { + label: t('eme.field.followRange'), type: 'number', + value: values.followRange, step: 1 + }, showEntity), + + rendering_header: advancedField( + {type: 'info', text: `### ${t('eme.section.rendering')}`}, + showRender), + scale: advancedField( + { + label: t('eme.field.scale'), type: 'number', value: values.scale, + step: 0.1 + }, showRender), + shadowRadius: advancedField( + { + label: t('eme.field.shadowRadius'), type: 'number', + value: values.shadowRadius, step: 0.1 + }, showRender), + visibleBoundsWidth: advancedField( + { + label: t('eme.field.visibleBoundsWidth'), type: 'number', + value: values.visibleBoundsWidth, step: 0.1 + }, showRender), + visibleBoundsHeight: advancedField( + { + label: t('eme.field.visibleBoundsHeight'), type: 'number', + value: values.visibleBoundsHeight, step: 0.1 + }, showRender), + visibleBoundsOffset: advancedField( + { + label: t('eme.field.visibleBoundsOffset'), type: 'vector', + value: values.visibleBoundsOffset + }, showRender), + + animation_header: advancedField( + {type: 'info', text: `### ${t('eme.section.animation')}`}, + showRender), + animationMode: advancedField({ + label: t('eme.field.animationMode'), + type: 'select', + options: animationModeOptions(), + value: values.animationMode + }, showRender), + swingSpeed: advancedField( + { + label: t('eme.field.swingSpeed'), type: 'number', + value: values.swingSpeed, step: 0.1 + }, showRender), + walkSpeedMultiplier: advancedField( + { + label: t('eme.field.walkSpeedMultiplier'), type: 'number', + value: values.walkSpeedMultiplier, step: 0.1 + }, showRender) + }); + + return config; +} + +function openExportDialog(options) { + const settings = options.settings; + const modelDimensions = options.modelDimensions; + // Tracks the active model type + preset so advanced fields refill whenever the + // user switches between entity/block entity or picks a different preset. + let lastKey = `${activeModelType(options)}|${activePreset(options)}`; + + const dialog = new Dialog({ + id: 'eme_export_dialog', + title: t('eme.dialog.title'), + width: 560, + form: buildFormConfig(settings, { + preset: options.preset, + blockPreset: options.blockPreset, + modelType: options.modelType, + exportType: options.exportType, + customize: options.customize, + showCustomization: options.showCustomization, + experimental: options.experimental + }), + onFormChange(form) { + const modelType = activeModelType(form); + const preset = activePreset(form); + const key = `${modelType}|${preset}`; + if (key !== lastKey) { + lastKey = key; + const isCustomEntity = modelType === MODEL_TYPE_ENTITY + && preset === 'custom'; + if (!isCustomEntity) { + dialog.setFormValues( + presetFormValues(preset, modelType, modelDimensions), false); + } + } + }, + onConfirm(form) { + const finalSettings = resolveExportSettings(form, settings, + modelDimensions); + // Records that experimental presets were available for this export. + finalSettings.experimental = !!options.experimental; + options.onExport(finalSettings, finalSettings.exportTarget); + } + }); + dialog.show(); + return dialog; +} + +module.exports = { + presetOptions, + settingsToForm, + formToSettings, + presetFormValues, + resolveExportSettings, + openExportDialog +}; diff --git a/plugins/easy_model_entities/src/utils/ResourceLocation.js b/plugins/easy_model_entities/src/utils/ResourceLocation.js new file mode 100644 index 00000000..20a92088 --- /dev/null +++ b/plugins/easy_model_entities/src/utils/ResourceLocation.js @@ -0,0 +1,70 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +class ResourceLocation { + static #NAMESPACE_PATTERN = /^[a-z0-9_.-]+$/; + static #PATH_PATTERN = /^[a-z0-9_./-]+$/; + + static isValidNamespace(namespace) { + return typeof namespace === 'string' + && ResourceLocation.#NAMESPACE_PATTERN.test(namespace); + } + + static isValidPath(path) { + return typeof path === 'string' && ResourceLocation.#PATH_PATTERN.test(path) + && !path.includes('..'); + } + + static isValidResourceLocation(value) { + if (typeof value !== 'string' || !value.includes(':')) { + return false; + } + const parts = value.split(':'); + if (parts.length !== 2) { + return false; + } + return ResourceLocation.isValidNamespace(parts[0]) + && ResourceLocation.isValidPath(parts[1]); + } + + static parseResourceLocation(value) { + if (!ResourceLocation.isValidResourceLocation(value)) { + return null; + } + const parts = value.split(':'); + return {namespace: parts[0], path: parts[1]}; + } + + static buildResourceLocation(namespace, path) { + return `${namespace}:${path}`; + } + + // e.g. "My Model.bbmodel" -> "my_model" + static sanitizeProfileId(name) { + const base = String(name || '') + .replace(/\.[^.]+$/, '') + .toLowerCase() + .replace(/[^a-z0-9_./-]+/g, '_') + .replace(/_{2,}/g, '_') + .replace(/^[_./-]+|[_./-]+$/g, ''); + return base.length > 0 ? base : 'entity'; + } +} + +module.exports = {ResourceLocation}; diff --git a/plugins/easy_model_entities/src/webpack.config.js b/plugins/easy_model_entities/src/webpack.config.js new file mode 100644 index 00000000..e3983758 --- /dev/null +++ b/plugins/easy_model_entities/src/webpack.config.js @@ -0,0 +1,48 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const path = require('path'); +const CopyWebpackPlugin = require('copy-webpack-plugin'); + +module.exports = { + entry: './index.js', + output: { + filename: 'easy_model_entities.js', + path: path.resolve(__dirname, '..') + }, + target: 'node', + module: { + rules: [ + {test: /\.svg$/, type: 'asset/source'}, + {test: /\.md$/, type: 'asset/source'} + ] + }, + plugins: [ + new CopyWebpackPlugin({ + patterns: [ + {from: './LICENSE.MD', to: './LICENSE.MD', noErrorOnMissing: true}, + {from: './resources/icon.png', to: './icon.png', noErrorOnMissing: true} + ] + }) + ], + mode: 'production', + optimization: { + minimize: false + } +}; From bee5fe0ed3f769502affc5a9736c69dcd40532ff Mon Sep 17 00:00:00 2001 From: Markus Bordihn Date: Tue, 16 Jun 2026 19:55:04 +0200 Subject: [PATCH 2/8] Fixed exporter to only show up for currently supported formats. Removed no longer needed fields and related functions. Added own Easy Model Entity format, based on Modded Entity format with new icon. Added face UV support for Easy Model Entity format. Added multiple texture support for Easy Model Entity Format. Added auto-generation for specific fields and options. Refactored and clean up code by removing unused variables and functions. updating texture handling, and improving readability --- plugins.json | 8 +- plugins/easy_model_entities/about.md | 8 +- .../easy_model_entities.js | 5944 +++++++++-------- .../src/BlockbenchAdapter.js | 56 +- .../src/builders/exporter.js | 98 +- .../src/builders/profileDiff.js | 7 +- .../src/builders/readme.js | 11 +- .../src/builders/renderProfile.js | 45 +- .../src/builders/serverProfile.js | 23 +- .../src/format/EmeCodecHooks.js | 58 + .../src/format/EmeFormat.js | 72 + .../src/i18n/translations.js | 9 +- plugins/easy_model_entities/src/index.js | 72 +- .../src/model/ModelDimensions.js | 44 +- .../src/model/PresetDetector.js | 12 +- .../src/model/Validator.js | 24 +- .../src/model/presetTypes.js | 75 +- .../src/model/templates.js | 6 +- .../src/resources/action_icon.svg | 34 - .../src/resources/create_icon.svg | 1 + .../src/resources/export_icon.svg | 1 + .../src/resources/readme.md | 28 +- .../src/tests/exportDialog.test.js | 31 +- .../src/tests/exporter.test.js | 113 +- .../src/tests/fixtureData.js | 18 +- .../src/tests/mdTransform.js | 19 + .../src/tests/modelDimensions.test.js | 54 +- .../src/tests/presetDetector.test.js | 19 + .../src/tests/readme.test.js | 31 + .../src/tests/resourceLocation.test.js | 19 + .../src/tests/schemaConsistency.test.js | 21 +- .../src/tests/templates.test.js | 32 +- .../src/tests/textureResolver.test.js | 85 + .../src/tests/validation.test.js | 24 +- .../src/tests/versionMatrix.test.js | 19 + .../src/ui/exportDialog.js | 53 +- .../src/utils/TextureResolver.js | 104 + plugins/easy_model_entities/src/utils/hash.js | 33 + 38 files changed, 4051 insertions(+), 3260 deletions(-) create mode 100644 plugins/easy_model_entities/src/format/EmeCodecHooks.js create mode 100644 plugins/easy_model_entities/src/format/EmeFormat.js delete mode 100644 plugins/easy_model_entities/src/resources/action_icon.svg create mode 100644 plugins/easy_model_entities/src/resources/create_icon.svg create mode 100644 plugins/easy_model_entities/src/resources/export_icon.svg create mode 100644 plugins/easy_model_entities/src/tests/textureResolver.test.js create mode 100644 plugins/easy_model_entities/src/utils/TextureResolver.js create mode 100644 plugins/easy_model_entities/src/utils/hash.js diff --git a/plugins.json b/plugins.json index 722c49e3..d5c92362 100644 --- a/plugins.json +++ b/plugins.json @@ -1540,12 +1540,16 @@ "author": "Markus Bordihn", "icon": "icon.png", "description": "Export Blockbench models for the Easy Model Entities mod (Minecraft: Java Edition) as ready-to-install Data Pack and Resource Pack files.", - "tags": ["Minecraft: Java Edition", "Exporter", "Entity"], + "tags": ["Minecraft: Java Edition", "Format", "Exporter", "Entity"], "version": "0.1.0", "min_version": "4.9.0", "variant": "desktop", + "await_loading": true, "has_changelog": true, - "website": "https://github.com/MarkusBordihn/BOs-Easy-Model-Entities", + "contributes": { + "formats": ["eme_entity"] + }, + "website": "https://www.curseforge.com/minecraft/mc-mods/easy-model-entities", "repository": "https://github.com/MarkusBordihn/BOs-Easy-Model-Entities", "bug_tracker": "https://github.com/MarkusBordihn/BOs-Easy-Model-Entities/issues" } diff --git a/plugins/easy_model_entities/about.md b/plugins/easy_model_entities/about.md index 8cfdcffb..25fc7c24 100644 --- a/plugins/easy_model_entities/about.md +++ b/plugins/easy_model_entities/about.md @@ -13,8 +13,9 @@ installed** in Minecraft for them to work. Without the mod the generated files d ## How to use -1. Open or create your model in Blockbench as a **Modded Entity** (File > New > Java/Modded Entity). - Block and item formats are not supported. +1. Open or create your model in Blockbench using the **Easy Model Entity** format + (File > New > Easy Model Entity), this is the recommended format for this plugin. + Alternatively, the built-in **Modded Entity** format (File > New > Java/Modded Entity) is also supported. Block and item formats are not supported. 2. Run **File > Export > Export Easy Model Entities**. 3. Choose the **Export Type**: - **Standalone: Data Pack + Resource Pack (ZIP)** — a single archive with a `datapack.zip` and a @@ -35,9 +36,6 @@ installed** in Minecraft for them to work. Without the mod the generated files d Exported settings are saved inside the `.bbmodel` project and restored on the next export. -> Note: `.bbmodel` is used as the runtime model format. It may be replaced by a dedicated format -> in a future version of the mod. - ## Blockbench settings Two optional settings are available under **File > Preferences > Settings > Export**: diff --git a/plugins/easy_model_entities/easy_model_entities.js b/plugins/easy_model_entities/easy_model_entities.js index a45520a8..ad6e8b57 100644 --- a/plugins/easy_model_entities/easy_model_entities.js +++ b/plugins/easy_model_entities/easy_model_entities.js @@ -1,3102 +1,3304 @@ -/******/ -(() => { // webpackBootstrap - /******/ - var __webpack_modules__ = ({ - - /***/ 833 - (module, __unused_webpack_exports, __webpack_require__) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - class BlockbenchAdapter { - static PROJECT_PROPERTY = 'eme_export'; - - static #base64ToBytes(base64) { - const clean = String(base64).replace(/^data:[^,]*,/, ''); - const binary = - typeof atob === 'function' ? atob(clean) : Buffer.from(clean, - 'base64').toString('binary'); - const bytes = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i++) { - bytes[i] = binary.codePointAt(i) & 0xff; - } - return bytes; - } +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 833 +(module, __unused_webpack_exports, __webpack_require__) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +class BlockbenchAdapter { + static PROJECT_PROPERTY = 'eme_export'; + + static #base64ToBytes(base64) { + const clean = String(base64).replace(/^data:[^,]*,/, ''); + const binary = + typeof atob === 'function' ? atob(clean) : Buffer.from(clean, + 'base64').toString('binary'); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.codePointAt(i) & 0xff; + } - // The texture currently selected/active in Blockbench; only this texture is - // exported when the project contains multiple textures. - static getActiveTexture() { - if (typeof Texture === 'undefined') { - return null; - } - return Texture.selected || Texture.getDefault() || (Texture.all?.[0]) - || null; - } + return bytes; + } - static getTextureCount() { - if (typeof Texture === 'undefined' || !Texture.all) { - return 0; - } - return Texture.all.length; - } + static #firstTexture() { + if (typeof Texture === 'undefined') { + return null; + } - static getModelStats() { - const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; - const groups = typeof Group !== 'undefined' && Group.all ? Group.all - : []; - const texture = BlockbenchAdapter.getActiveTexture(); - - let maxDepth = 0; - groups.forEach((group) => { - let depth = 1; - let parent = group.parent; - while (parent && parent !== 'root' && typeof parent === 'object') { - depth += 1; - parent = parent.parent; - } - if (depth > maxDepth) { - maxDepth = depth; - } - }); - - return { - hasModel: cubes.length > 0 || groups.length > 0, - hasTexture: !!texture, - cubeCount: cubes.length, - boneCount: groups.length, - hierarchyDepth: maxDepth, - boneNames: groups.map((group) => group.name), - textureWidth: texture ? texture.width : undefined, - textureHeight: texture ? texture.height : undefined - }; - } + return Texture.getDefault?.() || Texture.all?.[0] || null; + } - static getModelBytes() { - return Codecs.project.compile({raw: false}); - } + static #textureIndex(texture, position) { + const id = parseInt(texture?.id, 10); - static getModelBounds() { - const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; - if (cubes.length === 0) { - return null; - } - const bounds = { - minX: Infinity, - minY: Infinity, - minZ: Infinity, - maxX: -Infinity, - maxY: -Infinity, - maxZ: -Infinity - }; - cubes.forEach((cube) => { - const from = cube.from || [0, 0, 0]; - const to = cube.to || [0, 0, 0]; - bounds.minX = Math.min(bounds.minX, from[0], to[0]); - bounds.minY = Math.min(bounds.minY, from[1], to[1]); - bounds.minZ = Math.min(bounds.minZ, from[2], to[2]); - bounds.maxX = Math.max(bounds.maxX, from[0], to[0]); - bounds.maxY = Math.max(bounds.maxY, from[1], to[1]); - bounds.maxZ = Math.max(bounds.maxZ, from[2], to[2]); - }); - return bounds; - } + return Number.isInteger(id) && id >= 0 ? id : position; + } - static getProjectName() { - if (typeof Project === 'undefined' || !Project) { - return ''; - } - if (Project.geometry_name) { - return Project.geometry_name; - } - if (Project.name) { - return Project.name; - } - if (typeof Project.getDisplayName === 'function') { - return Project.getDisplayName(false); - } - return ''; - } + // All project textures as plain descriptors for TextureResolver. External + // (vanilla / other-mod) textures are referenced by location; custom textures + // carry their PNG bytes to be packed. + static collectTextures() { + if (typeof Texture === 'undefined' || !Texture.all) { + return []; + } - static getTextureBytes() { - const texture = BlockbenchAdapter.getActiveTexture(); - if (!texture) { - return new Uint8Array(0); - } - return BlockbenchAdapter.#base64ToBytes(texture.getBase64()); - } + return Texture.all.map((texture, position) => ({ + index: BlockbenchAdapter.#textureIndex(texture, position), + name: texture.name || '', + namespace: texture.namespace || '', + folder: texture.folder || '', + path: texture.path || '', + bytes: BlockbenchAdapter.#base64ToBytes(texture.getBase64()) + })); + } - static loadSettings() { - if (typeof Project === 'undefined' || !Project) { - return null; - } - const stored = Project[BlockbenchAdapter.PROJECT_PROPERTY]; - if (stored && typeof stored === 'object') { - return structuredClone(stored); - } - return null; - } + static getModelStats() { + const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; + const groups = typeof Group !== 'undefined' && Group.all ? Group.all : []; + const texture = BlockbenchAdapter.#firstTexture(); - static saveSettings(settings) { - if (typeof Project !== 'undefined' && Project) { - Project[BlockbenchAdapter.PROJECT_PROPERTY] = structuredClone( - settings); - if (typeof Blockbench !== 'undefined' && Blockbench.dispatchEvent) { - Blockbench.dispatchEvent('eme_settings_saved', {settings}); - } - } - } + let maxDepth = 0; + groups.forEach((group) => { + let depth = 1; + let parent = group.parent; + while (parent && parent !== 'root' && typeof parent === 'object') { + depth += 1; + parent = parent.parent; + } + if (depth > maxDepth) { + maxDepth = depth; + } + }); - static #zipToUint8(files) { - const zip = new JSZip(); - files.forEach((file) => { - zip.file(file.path, file.content); - }); - return zip.generateAsync({type: 'uint8array'}); - } + return { + hasModel: cubes.length > 0 || groups.length > 0, + hasTexture: !!texture, + cubeCount: cubes.length, + boneCount: groups.length, + hierarchyDepth: maxDepth, + boneNames: groups.map((group) => group.name), + textureWidth: texture ? texture.width : undefined, + textureHeight: texture ? texture.height : undefined + }; + } - // Wraps a ready-to-drop datapack.zip and resourcepack.zip plus the README - // into a single outer ZIP, so the user can move the inner ZIPs straight into - // their datapacks/ and resourcepacks/ folders without unpacking. - static exportPackBundle(bundle, name) { - return Promise.all([ - BlockbenchAdapter.#zipToUint8(bundle.datapack), - BlockbenchAdapter.#zipToUint8(bundle.resourcepack) - ]).then(([datapackZip, resourcepackZip]) => { - const outer = new JSZip(); - outer.file(bundle.readme.path, bundle.readme.content); - outer.file('datapack.zip', datapackZip); - outer.file('resourcepack.zip', resourcepackZip); - return outer.generateAsync({type: 'blob'}); - }).then((content) => { - return new Promise((resolve) => { - Blockbench.export( - { - type: 'Zip Archive', - extensions: ['zip'], - name: name, - content: content, - savetype: 'zip' - }, - (path) => resolve(path) - ); - }); - }); - } + static getModelBytes() { + return Codecs.project.compile({raw: false}); + } - static listExistingFiles(rootDir, files) { - const fs = __webpack_require__(896); - const path = __webpack_require__(928); - return files - .map((file) => path.join(rootDir, file.path)) - .filter((fullPath) => fs.existsSync(fullPath)); - } + static getModelBounds() { + const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; + if (cubes.length === 0) { + return null; + } - static writeToDirectory(rootDir, files) { - const fs = __webpack_require__(896); - const path = __webpack_require__(928); - files.forEach((file) => { - const fullPath = path.join(rootDir, file.path); - fs.mkdirSync(path.dirname(fullPath), {recursive: true}); - const data = - file.binary && file.content instanceof Uint8Array ? Buffer.from( - file.content) : file.content; - fs.writeFileSync(fullPath, data); - }); - } + const bounds = { + minX: Infinity, + minY: Infinity, + minZ: Infinity, + maxX: -Infinity, + maxY: -Infinity, + maxZ: -Infinity + }; + cubes.forEach((cube) => { + const from = cube.from || [0, 0, 0]; + const to = cube.to || [0, 0, 0]; + bounds.minX = Math.min(bounds.minX, from[0], to[0]); + bounds.minY = Math.min(bounds.minY, from[1], to[1]); + bounds.minZ = Math.min(bounds.minZ, from[2], to[2]); + bounds.maxX = Math.max(bounds.maxX, from[0], to[0]); + bounds.maxY = Math.max(bounds.maxY, from[1], to[1]); + bounds.maxZ = Math.max(bounds.maxZ, from[2], to[2]); + }); - static pickDirectory(title) { - return Blockbench.pickDirectory( - {title: title, resource_id: 'eme_mod_project'}); - } - } + return bounds; + } - module.exports = {BlockbenchAdapter}; + static getProjectName() { + if (typeof Project === 'undefined' || !Project) { + return ''; + } - /***/ - }, + if (Project.geometry_name) { + return Project.geometry_name; + } - /***/ 869 - (module, __unused_webpack_exports, __webpack_require__) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - const {buildServerProfile} = __webpack_require__(790); - const {buildRenderProfile} = __webpack_require__(255); - const { - buildDataPackMcmeta, - buildResourcePackMcmeta - } = __webpack_require__(600); - const {buildReadme} = __webpack_require__(250); - - function toJson(value) { - return JSON.stringify(value, null, 2) + '\n'; - } + if (Project.name) { + return Project.name; + } - function buildProfiles(settings) { - return { - serverProfile: buildServerProfile(settings), - renderProfile: buildRenderProfile(settings) - }; - } + if (typeof Project.getDisplayName === 'function') { + return Project.getDisplayName(false); + } + return ''; + } - function dataPaths(settings) { - const ns = settings.namespace; - const id = settings.profileId; - // Server profiles live in an entity/ or block_entity/ subfolder. Models and - // textures stay at the mod's auto-resolved default location so the render - // profile never has to spell out a model/texture path. - const modelType = settings.modelType || 'entity'; - return { - profile: `data/${ns}/easy_model_entities/profiles/${modelType}/${id}.json`, - renderProfile: `assets/${ns}/easy_model_entities/render_profiles/${id}.json`, - model: `assets/${ns}/easy_model_entities/models/${id}.bbmodel`, - texture: `assets/${ns}/textures/entity/${id}.png` - }; - } + static loadSettings() { + if (typeof Project === 'undefined' || !Project) { + return null; + } - function datapackFiles(settings, serverProfile) { - return [ - { - path: 'pack.mcmeta', content: toJson(buildDataPackMcmeta(settings)), - binary: false - }, - { - path: dataPaths(settings).profile, content: toJson(serverProfile), - binary: false - } - ]; - } + const stored = Project[BlockbenchAdapter.PROJECT_PROPERTY]; + if (stored && typeof stored === 'object') { + return structuredClone(stored); + } - function resourcepackFiles(settings, renderProfile, options) { - const paths = dataPaths(settings); - return [ - { - path: 'pack.mcmeta', - content: toJson(buildResourcePackMcmeta(settings)), - binary: false - }, - { - path: paths.renderProfile, - content: toJson(renderProfile), - binary: false - }, - {path: paths.model, content: options.modelBytes, binary: true}, - {path: paths.texture, content: options.textureBytes, binary: true} - ]; - } + return null; + } -// Bundle to wrap into the outer export ZIP: a ready-to-drop datapack.zip and -// resourcepack.zip plus an install README. - function buildPackBundle(settings, options) { - const opts = options || {}; - const {serverProfile, renderProfile} = buildProfiles(settings); - return { - readme: { - path: 'README.md', - content: buildReadme(settings), - binary: false - }, - datapack: datapackFiles(settings, serverProfile), - resourcepack: resourcepackFiles(settings, renderProfile, opts), - serverProfile, - renderProfile - }; + static saveSettings(settings) { + if (typeof Project !== 'undefined' && Project) { + Project[BlockbenchAdapter.PROJECT_PROPERTY] = structuredClone( + settings); + if (typeof Blockbench !== 'undefined' && Blockbench.dispatchEvent) { + Blockbench.dispatchEvent('eme_settings_saved', {settings}); } + } + } - function buildModProjectFiles(settings, options) { - const opts = options || {}; - const renderProfile = buildRenderProfile(settings); - const paths = dataPaths(settings); - - const files = []; - let serverProfile = null; - // Model-only export (mod integration, e.g. the Mimic example): the mod ships - // its own entity classes, so no server profile / data pack is written. - if (!settings.modelOnly) { - serverProfile = buildServerProfile(settings); - files.push({ - path: paths.profile, content: toJson(serverProfile), - binary: false - }); - } + static #zipToUint8(files) { + const zip = new JSZip(); + files.forEach((file) => { + zip.file(file.path, file.content); + }); + + return zip.generateAsync({type: 'uint8array'}); + } - files.push( + // Wraps a ready-to-drop datapack.zip and resourcepack.zip plus the README + // into a single outer ZIP, so the user can move the inner ZIPs straight into + // their datapacks/ and resourcepacks/ folders without unpacking. + static exportPackBundle(bundle, name) { + return Promise.all([ + BlockbenchAdapter.#zipToUint8(bundle.datapack), + BlockbenchAdapter.#zipToUint8(bundle.resourcepack) + ]).then(([datapackZip, resourcepackZip]) => { + const outer = new JSZip(); + outer.file(bundle.readme.path, bundle.readme.content); + outer.file(bundle.datapackFileName || 'datapack.zip', datapackZip); + outer.file(bundle.resourcepackFileName || 'resourcepack.zip', + resourcepackZip); + return outer.generateAsync({type: 'blob'}); + }).then((content) => { + return new Promise((resolve) => { + Blockbench.export( { - path: paths.renderProfile, content: toJson(renderProfile), - binary: false + type: 'Zip Archive', + extensions: ['zip'], + name: name, + content: content, + savetype: 'zip' }, - {path: paths.model, content: opts.modelBytes, binary: true}, - {path: paths.texture, content: opts.textureBytes, binary: true}); - - return {files, serverProfile, renderProfile}; - } - - module.exports = { - buildPackBundle, - buildModProjectFiles, - dataPaths, - datapackFiles, - resourcepackFiles - }; - - /***/ - }, + (path) => resolve(path) + ); + }); + }); + } - /***/ 600 - (module, __unused_webpack_exports, __webpack_require__) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - const {getPackFormats} = __webpack_require__(954); - - function buildMcmeta(settings, packFormat, description) { - const formats = getPackFormats(settings.targetVersion); - if (!formats) { - throw new Error( - `Unsupported or disabled target version: ${settings.targetVersion}`); - } - return { - pack: { - pack_format: formats[packFormat], - description: description - } - }; - } + static listExistingFiles(rootDir, files) { + const fs = __webpack_require__(896); + const path = __webpack_require__(928); - function buildDataPackMcmeta(settings) { - return buildMcmeta(settings, 'dataFormat', - 'Easy Model Entities server profiles'); - } + return files + .map((file) => path.join(rootDir, file.path)) + .filter((fullPath) => fs.existsSync(fullPath)); + } - function buildResourcePackMcmeta(settings) { - return buildMcmeta(settings, 'resourceFormat', - 'Easy Model Entities render assets'); - } + static writeToDirectory(rootDir, files) { + const fs = __webpack_require__(896); + const path = __webpack_require__(928); + files.forEach((file) => { + const fullPath = path.join(rootDir, file.path); + fs.mkdirSync(path.dirname(fullPath), {recursive: true}); + const data = + file.binary && file.content instanceof Uint8Array ? Buffer.from( + file.content) : file.content; + fs.writeFileSync(fullPath, data); + }); + } - module.exports = {buildDataPackMcmeta, buildResourcePackMcmeta}; + static pickDirectory(title) { + return Blockbench.pickDirectory( + {title: title, resource_id: 'eme_mod_project'}); + } +} + +module.exports = {BlockbenchAdapter}; + + +/***/ }, + +/***/ 869 +(module, __unused_webpack_exports, __webpack_require__) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {buildServerProfile} = __webpack_require__(790); +const {buildRenderProfile} = __webpack_require__(255); +const {buildDataPackMcmeta, buildResourcePackMcmeta} = __webpack_require__(600); +const {buildReadme} = __webpack_require__(250); +const {hashString} = __webpack_require__(803); + +function toJson(value) { + return JSON.stringify(value, null, 2) + '\n'; +} + +// The version stamp is a content hash of the server profile, i.e. of exactly +// the server-relevant settings (type, preset, dimensions, behavior, …) and not +// of the model geometry or textures. Both profiles receive the same stamp so +// the mod's server/client parity check always matches; it only changes when a +// server-relevant setting changes. Model-only exports have no server profile +// and stay version-less (the mod's own runtime contract owns the version). +function stampVersion(serverProfile, renderProfile) { + if (!serverProfile) { + return; + } - /***/ + const version = hashString(toJson(serverProfile)); + serverProfile.version = version; + renderProfile.version = version; +} + +function buildProfiles(settings, textureResolution) { + const serverProfile = buildServerProfile(settings); + const renderProfile = buildRenderProfile(settings, textureResolution); + stampVersion(serverProfile, renderProfile); + + return {serverProfile, renderProfile}; +} + +function dataPaths(settings) { + const namespace = settings.namespace; + const id = settings.profileId; + // Server profiles live in an entity/ or block_entity/ subfolder. The model + // stays at the mod's auto-resolved default location so the render profile + // never has to spell out a model path. + const modelType = settings.modelType || 'entity'; + + return { + profile: `data/${namespace}/easy_model_entities/profiles/${modelType}/${id}.json`, + renderProfile: `assets/${namespace}/easy_model_entities/render_profiles/${id}.json`, + model: `assets/${namespace}/easy_model_entities/models/${id}.bbmodel` + }; +} + +// One binary entry per custom (packed) texture; external textures are +// referenced by resource location and never written. +function textureFiles(settings, textureResolution) { + const namespace = settings.namespace; + const packed = (textureResolution && textureResolution.packed) || []; + + return packed.map((entry) => ({ + path: `assets/${namespace}/textures/entity/${entry.fileName}`, + content: entry.bytes, + binary: true + })); +} + +function datapackFiles(settings, serverProfile) { + return [ + { + path: 'pack.mcmeta', content: toJson(buildDataPackMcmeta(settings)), + binary: false + }, + { + path: dataPaths(settings).profile, content: toJson(serverProfile), + binary: false + } + ]; +} + +function resourcepackFiles(settings, renderProfile, options) { + const paths = dataPaths(settings); + return [ + { + path: 'pack.mcmeta', content: toJson(buildResourcePackMcmeta(settings)), + binary: false }, + {path: paths.renderProfile, content: toJson(renderProfile), binary: false}, + {path: paths.model, content: options.modelBytes, binary: true}, + ...textureFiles(settings, options.textureResolution) + ]; +} + +// Inner pack file names are prefixed with the profile id so a user dropping +// several exported packs into one folder never overwrites another model's pack. +function packFileNames(settings) { + return { + datapack: `${settings.profileId}_datapack.zip`, + resourcepack: `${settings.profileId}_resourcepack.zip` + }; +} + +function buildPackBundle(settings, options) { + const opts = options || {}; + const {serverProfile, renderProfile} = buildProfiles(settings, + opts.textureResolution); + const fileNames = packFileNames(settings); + + return { + readme: { + path: 'README.md', content: buildReadme(settings, fileNames), + binary: false + }, + datapack: datapackFiles(settings, serverProfile), + resourcepack: resourcepackFiles(settings, renderProfile, opts), + datapackFileName: fileNames.datapack, + resourcepackFileName: fileNames.resourcepack, + serverProfile, + renderProfile + }; +} + +function buildModProjectFiles(settings, options) { + const opts = options || {}; + const renderProfile = buildRenderProfile(settings, opts.textureResolution); + const paths = dataPaths(settings); + + const files = []; + // Model-only export (mod integration, e.g. the Mimic example): the mod ships + // its own entity classes, so no server profile / data pack is written. + const serverProfile = settings.modelOnly ? null : buildServerProfile( + settings); + stampVersion(serverProfile, renderProfile); + if (serverProfile) { + files.push({ + path: paths.profile, content: toJson(serverProfile), + binary: false + }); + } - /***/ 640 - (module) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ + files.push( + { + path: paths.renderProfile, content: toJson(renderProfile), + binary: false + }, + {path: paths.model, content: opts.modelBytes, binary: true}, + ...textureFiles(settings, opts.textureResolution)); + + return {files, serverProfile, renderProfile}; +} + +module.exports = { + buildPackBundle, + buildModProjectFiles +}; + + +/***/ }, + +/***/ 600 +(module, __unused_webpack_exports, __webpack_require__) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {getPackFormats} = __webpack_require__(954); + +function buildMcmeta(settings, packFormat, description) { + const formats = getPackFormats(settings.targetVersion); + if (!formats) { + throw new Error( + `Unsupported or disabled target version: ${settings.targetVersion}`); + } + return { + pack: { + pack_format: formats[packFormat], + description: description + } + }; +} + +function buildDataPackMcmeta(settings) { + return buildMcmeta(settings, 'dataFormat', + 'Easy Model Entities server profiles'); +} + +function buildResourcePackMcmeta(settings) { + return buildMcmeta(settings, 'resourceFormat', + 'Easy Model Entities render assets'); +} + +module.exports = {buildDataPackMcmeta, buildResourcePackMcmeta}; + + +/***/ }, + +/***/ 640 +(module) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ // Helpers to emit minimal (diff-based) profiles: only values that deviate from // the defaults the mod derives from preset_type are written. - function valuesDiffer(a, b) { - if (Array.isArray(a) || Array.isArray(b)) { - return JSON.stringify(a) !== JSON.stringify(b); - } - return a !== b; - } - -// Returns a new object containing only the keys whose value differs from the -// matching key in defaults. - function diffFlat(values, defaults) { - const result = {}; - Object.keys(values).forEach((key) => { - if (valuesDiffer(values[key], defaults[key])) { - result[key] = values[key]; - } - }); - return result; - } - - function isEmpty(object) { - return !object || Object.keys(object).length === 0; - } - -// Assigns section to target under key only when it has at least one entry. - function assignIfPresent(target, key, section) { - if (!isEmpty(section)) { - target[key] = section; - } - } - - module.exports = {valuesDiffer, diffFlat, isEmpty, assignIfPresent}; +function valuesDiffer(a, b) { + if (Array.isArray(a) || Array.isArray(b)) { + return JSON.stringify(a) !== JSON.stringify(b); + } - /***/ - }, + return a !== b; +} - /***/ 250 - (module, __unused_webpack_exports, __webpack_require__) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - const {ResourceLocation} = __webpack_require__(20); - const TEMPLATE = __webpack_require__(377); - - function buildReadme(settings) { - const id = ResourceLocation.buildResourceLocation(settings.namespace, - settings.profileId); - const modelType = settings.modelType || 'entity'; - const serverProfileId = ResourceLocation.buildResourceLocation( - settings.namespace, `${modelType}/${settings.profileId}`); - return TEMPLATE - .replaceAll('{{id}}', id) - .replaceAll('{{serverProfileId}}', serverProfileId) - .replaceAll('{{mcVersion}}', - `Minecraft: Java Edition ${settings.targetVersion}`) - .replaceAll('{{folderName}}', `${settings.namespace}_eme`); - } +function diffFlat(values, defaults) { + const result = {}; + Object.keys(values).forEach((key) => { + if (valuesDiffer(values[key], defaults[key])) { + result[key] = values[key]; + } + }); - module.exports = {buildReadme}; + return result; +} - /***/ - }, +function isEmpty(object) { + return !object || Object.keys(object).length === 0; +} - /***/ 255 - (module, __unused_webpack_exports, __webpack_require__) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - const { - bodyType, - animationMode, - presetRenderBounds, - isCustom, - MODEL_TYPE_BLOCK_ENTITY - } = __webpack_require__(151); - const {diffFlat, assignIfPresent} = __webpack_require__(640); +function assignIfPresent(target, key, section) { + if (!isEmpty(section)) { + target[key] = section; + } +} + +module.exports = {diffFlat, assignIfPresent}; + + +/***/ }, + +/***/ 250 +(module, __unused_webpack_exports, __webpack_require__) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {ResourceLocation} = __webpack_require__(20); +const TEMPLATE = __webpack_require__(377); + +function buildReadme(settings, fileNames) { + const id = ResourceLocation.buildResourceLocation(settings.namespace, + settings.profileId); + const modelType = settings.modelType || 'entity'; + const serverProfileId = ResourceLocation.buildResourceLocation( + settings.namespace, `${modelType}/${settings.profileId}`); + const names = fileNames || { + datapack: `${settings.profileId}_datapack.zip`, + resourcepack: `${settings.profileId}_resourcepack.zip` + }; + + return TEMPLATE + .replaceAll('{{id}}', id) + .replaceAll('{{serverProfileId}}', serverProfileId) + .replaceAll('{{mcVersion}}', + `Minecraft: Java Edition ${settings.targetVersion}`) + .replaceAll('{{folderName}}', `${settings.namespace}_eme`) + .replaceAll('{{datapackFile}}', names.datapack) + .replaceAll('{{resourcepackFile}}', names.resourcepack); +} + +module.exports = {buildReadme}; + + +/***/ }, + +/***/ 255 +(module, __unused_webpack_exports, __webpack_require__) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const { + bodyType, + animationMode, + presetShadowRadius, + isCustom, + MODEL_TYPE_BLOCK_ENTITY +} = __webpack_require__(151); +const {diffFlat, assignIfPresent} = __webpack_require__(640); // Block entities render as a static cuboid block, so they always use the // "static" render preset regardless of their server-side block entity preset. - function renderPresetType(settings) { - return settings.modelType === MODEL_TYPE_BLOCK_ENTITY - ? 'static' : settings.presetType; - } +function renderPresetType(settings) { + return settings.modelType === MODEL_TYPE_BLOCK_ENTITY + ? 'static' : settings.presetType; +} + +function buildRendering(settings) { + return diffFlat({ + scale: settings.rendering.scale, + shadow_radius: settings.rendering.shadowRadius + }, { + scale: 1, + shadow_radius: presetShadowRadius(renderPresetType(settings)) + }); +} + +function buildAnimation(settings) { + const defaultMode = animationMode(renderPresetType(settings)); + const animation = diffFlat({ + mode: settings.animation.mode, + swing_speed: settings.animation.swingSpeed, + walk_speed_multiplier: settings.animation.walkSpeedMultiplier + }, { + mode: defaultMode, + swing_speed: 1, + walk_speed_multiplier: 1 + }); + // Animation timing is meaningless when animation is disabled. + if (settings.animation.mode === 'none') { + delete animation.swing_speed; + delete animation.walk_speed_multiplier; + } - function buildModelReference(settings) { - return `${settings.namespace}:easy_model_entities/models/${settings.profileId}`; - } + return animation; +} - function buildTextureReference(settings) { - return `${settings.namespace}:textures/entity/${settings.profileId}.png`; - } +function buildRenderProfile(settings, textureResolution) { + const renderPreset = renderPresetType(settings); + const custom = settings.modelType !== MODEL_TYPE_BLOCK_ENTITY + && isCustom(settings.presetType); + const profile = { + schema_version: settings.schemaVersion, + preset_type: renderPreset + }; - function buildRendering(settings) { - const [boundsWidth, boundsHeight, boundsOffsetY, shadowRadius] = - presetRenderBounds(renderPresetType(settings)); - return diffFlat({ - scale: settings.rendering.scale, - shadow_radius: settings.rendering.shadowRadius, - visible_bounds_width: settings.rendering.visibleBoundsWidth, - visible_bounds_height: settings.rendering.visibleBoundsHeight, - visible_bounds_offset: settings.rendering.visibleBoundsOffset.slice() - }, { - scale: 1, - shadow_radius: shadowRadius, - visible_bounds_width: boundsWidth, - visible_bounds_height: boundsHeight, - visible_bounds_offset: [0, boundsOffsetY, 0] - }); - } + if (custom || settings.host.bodyType !== bodyType(renderPreset)) { + profile.body_type = settings.host.bodyType; + } - function buildAnimation(settings) { - const defaultMode = animationMode(renderPresetType(settings)); - const animation = diffFlat({ - mode: settings.animation.mode, - swing_speed: settings.animation.swingSpeed, - walk_speed_multiplier: settings.animation.walkSpeedMultiplier - }, { - mode: defaultMode, - swing_speed: 1, - walk_speed_multiplier: 1 - }); - // Animation timing is meaningless when animation is disabled. - if (settings.animation.mode === 'none') { - delete animation.swing_speed; - delete animation.walk_speed_multiplier; - } - return animation; - } + // The model and the index-0 texture follow the mod's conventional default + // path (namespace:easy_model_entities/models/id and + // namespace:textures/entity/id.png), so they are omitted whenever they match + // the default. Only deviating texture locations (e.g. vanilla index 0 or any + // index > 0) are spelled out for the mod's ModelTextureResolver. + if (textureResolution && textureResolution.texture) { + profile.texture = textureResolution.texture; + } + assignIfPresent(profile, 'textures', + textureResolution && textureResolution.textures); + + assignIfPresent(profile, 'rendering', buildRendering(settings)); + assignIfPresent(profile, 'animation', buildAnimation(settings)); + + return profile; +} + +module.exports = { + buildRenderProfile +}; + + +/***/ }, + +/***/ 790 +(module, __unused_webpack_exports, __webpack_require__) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const { + presetDefaults, + presetDimensions, + movementDefaults, + behaviorModeFor, + isCustom, + MODEL_TYPE_BLOCK_ENTITY, + MODEL_TYPE_ENTITY +} = __webpack_require__(151); +const {diffFlat, assignIfPresent} = __webpack_require__(640); + +const DEFAULT_MAX_HEALTH = 10; +const DEFAULT_FOLLOW_RANGE = 16; + +function effectiveDefaults(settings) { + const preset = settings.presetType; + const movementType = settings.host.movementType; + const movement = movementDefaults(preset, movementType); + const mode = behaviorModeFor(preset, movementType); + + return { + movement: { + speed: movement.speed, + step_height: movement.stepHeight, + gravity: movement.gravity + }, + behavior: { + mode: mode, + look_at_players: mode === 'idle_only' || mode === 'ambient', + random_stroll: movementType === 'ground' && mode === 'ambient' + }, + attributes: { + max_health: DEFAULT_MAX_HEALTH, + movement_speed: settings.movement.speed, + follow_range: DEFAULT_FOLLOW_RANGE + }, + dimensions: settings.modelType === MODEL_TYPE_BLOCK_ENTITY + ? presetDefaults(preset, MODEL_TYPE_BLOCK_ENTITY).dimensions + : presetDimensions(preset) + }; +} - function buildRenderProfile(settings) { - const renderPreset = renderPresetType(settings); - const custom = settings.modelType !== MODEL_TYPE_BLOCK_ENTITY - && isCustom(settings.presetType); - const profile = { - schema_version: settings.schemaVersion, - preset_type: renderPreset - }; - if (settings.version) { - profile.version = settings.version; - } +// Entity host settings. The mod derives type/movement_type/body_type from the +// preset, so only the "custom" preset needs to spell them out; non-custom +// presets emit nothing here. +function buildEntity(settings, custom) { + if (!custom) { + return {}; + } - if (custom || settings.host.bodyType !== bodyType(renderPreset)) { - profile.body_type = settings.host.bodyType; - } + const host = settings.host; + + return { + type: host.entityType, + movement_type: host.movementType, + body_type: host.bodyType + }; +} + +function buildDimensions(settings, defaults, custom) { + const values = { + width: settings.dimensions.width, + height: settings.dimensions.height, + eye_height: settings.dimensions.eyeHeight + }; + if (custom) { + return values; + } - // model and texture follow the mod's conventional default path - // (namespace:easy_model_entities/models/id and namespace:textures/entity/id.png), - // so they are omitted and resolved automatically by the mod. + return diffFlat(values, { + width: defaults.dimensions.width, + height: defaults.dimensions.height, + eye_height: defaults.dimensions.eyeHeight + }); +} + +function buildServerProfile(settings) { + const modelType = settings.modelType || MODEL_TYPE_ENTITY; + const blockEntity = modelType === MODEL_TYPE_BLOCK_ENTITY; + const custom = !blockEntity && isCustom(settings.presetType); + const defaults = effectiveDefaults(settings); + + const profile = { + schema_version: settings.schemaVersion, + model_type: modelType, + preset_type: settings.presetType + }; + + // The server profile lives in a "/" subfolder, so its derived + // id differs from the flat render profile id. The render profile must be named + // explicitly or the mod would look for "/" instead. + profile.client = { + render_profile: `${settings.namespace}:${settings.profileId}` + }; + + if (!blockEntity) { + assignIfPresent(profile, 'entity', buildEntity(settings, custom)); + } - assignIfPresent(profile, 'rendering', buildRendering(settings)); - assignIfPresent(profile, 'animation', buildAnimation(settings)); + assignIfPresent(profile, 'dimensions', + buildDimensions(settings, defaults, custom)); + + if (!blockEntity) { + assignIfPresent(profile, 'movement', diffFlat({ + speed: settings.movement.speed, + step_height: settings.movement.stepHeight, + gravity: settings.movement.gravity + }, defaults.movement)); + + assignIfPresent(profile, 'behavior', diffFlat({ + mode: settings.behavior.mode, + look_at_players: settings.behavior.lookAtPlayers, + random_stroll: settings.behavior.randomStroll + }, defaults.behavior)); + + assignIfPresent(profile, 'attributes', diffFlat({ + max_health: settings.attributes.maxHealth, + movement_speed: settings.attributes.movementSpeed, + follow_range: settings.attributes.followRange + }, defaults.attributes)); + } - return profile; - } + return profile; +} - module.exports = { - buildRenderProfile, - buildModelReference, - buildTextureReference - }; +module.exports = {buildServerProfile}; - /***/ - }, - /***/ 790 - (module, __unused_webpack_exports, __webpack_require__) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - const { - presetDefaults, - presetDimensions, - movementDefaults, - behaviorModeFor, - isCustom, - MODEL_TYPE_BLOCK_ENTITY, - MODEL_TYPE_ENTITY - } = __webpack_require__(151); - const {diffFlat, assignIfPresent} = __webpack_require__(640); - - const DEFAULT_MAX_HEALTH = 10; - const DEFAULT_FOLLOW_RANGE = 16; - - function effectiveDefaults(settings) { - const preset = settings.presetType; - const move = settings.host.movementType; - const movement = movementDefaults(preset, move); - const mode = behaviorModeFor(preset, move); - const dimensions = settings.modelType === MODEL_TYPE_BLOCK_ENTITY - ? presetDefaults(preset, MODEL_TYPE_BLOCK_ENTITY).dimensions - : presetDimensions(preset); - return { - movement: { - speed: movement.speed, - step_height: movement.stepHeight, - gravity: movement.gravity - }, - behavior: { - mode: mode, - look_at_players: mode === 'idle_only' || mode === 'ambient', - random_stroll: move === 'ground' && mode === 'ambient' - }, - attributes: { - max_health: DEFAULT_MAX_HEALTH, - movement_speed: settings.movement.speed, - follow_range: DEFAULT_FOLLOW_RANGE - }, - dimensions: dimensions - }; - } +/***/ }, -// Entity host settings. The mod derives type/movement_type/body_type from the -// preset, so only the "custom" preset needs to spell them out; non-custom -// presets emit nothing here. - function buildEntity(settings, custom) { - if (!custom) { - return {}; - } - const host = settings.host; - return { - type: host.entityType, - movement_type: host.movementType, - body_type: host.bodyType - }; - } +/***/ 670 +(module, __unused_webpack_exports, __webpack_require__) { - function buildDimensions(settings, defaults, custom) { - const values = { - width: settings.dimensions.width, - height: settings.dimensions.height, - eye_height: settings.dimensions.eyeHeight - }; - if (custom) { - return values; - } - return diffFlat(values, { - width: defaults.dimensions.width, - height: defaults.dimensions.height, - eye_height: defaults.dimensions.eyeHeight - }); - } +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ - function buildServerProfile(settings) { - const modelType = settings.modelType || MODEL_TYPE_ENTITY; - const blockEntity = modelType === MODEL_TYPE_BLOCK_ENTITY; - const custom = !blockEntity && isCustom(settings.presetType); - const defaults = effectiveDefaults(settings); - - const profile = { - schema_version: settings.schemaVersion, - model_type: modelType, - preset_type: settings.presetType - }; - if (settings.version) { - profile.version = settings.version; - } +const {BlockbenchAdapter} = __webpack_require__(833); +const {FORMAT_ID} = __webpack_require__(995); - // The server profile lives in a "/" subfolder, so its derived - // id differs from the flat render profile id. The render profile must be named - // explicitly or the mod would look for "/" instead. - profile.client = { - render_profile: `${settings.namespace}:${settings.profileId}` - }; +const EME_SETTINGS_KEY = 'emeSettings'; - if (!blockEntity) { - assignIfPresent(profile, 'entity', buildEntity(settings, custom)); - } +function handleCompile(event) { + if (Format?.id !== FORMAT_ID) { + return; + } - assignIfPresent(profile, 'dimensions', - buildDimensions(settings, defaults, custom)); - - // Block entities ignore movement, behavior and attributes. - if (!blockEntity) { - assignIfPresent(profile, 'movement', diffFlat({ - speed: settings.movement.speed, - step_height: settings.movement.stepHeight, - gravity: settings.movement.gravity - }, defaults.movement)); - - assignIfPresent(profile, 'behavior', diffFlat({ - mode: settings.behavior.mode, - look_at_players: settings.behavior.lookAtPlayers, - random_stroll: settings.behavior.randomStroll - }, defaults.behavior)); - - assignIfPresent(profile, 'attributes', diffFlat({ - max_health: settings.attributes.maxHealth, - movement_speed: settings.attributes.movementSpeed, - follow_range: settings.attributes.followRange - }, defaults.attributes)); - } + const settings = BlockbenchAdapter.loadSettings(); + if (settings) { + event.model[EME_SETTINGS_KEY] = settings; + } +} - return profile; - } +function handleParse(event) { + const formatId = event.model?.meta?.model_format || event.model?.model_format; + if (formatId !== FORMAT_ID) { + return; + } - module.exports = {buildServerProfile}; + const settings = event.model[EME_SETTINGS_KEY]; + if (settings && typeof settings === 'object') { + BlockbenchAdapter.saveSettings(settings); + } +} + +function registerEmeCodecHooks() { + Codecs.project.on('compile', handleCompile); + Codecs.project.on('parse', handleParse); +} + +function unregisterEmeCodecHooks() { + Codecs.project.events?.compile?.remove?.(handleCompile); + Codecs.project.events?.parse?.remove?.(handleParse); +} + +module.exports = {registerEmeCodecHooks, unregisterEmeCodecHooks}; + + +/***/ }, + +/***/ 995 +(module) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const FORMAT_ID = 'eme_entity'; + +let emeFormat = null; + +function toModelIdentifier(name) { + return name.trim().toLowerCase() + .replace(/\s+/g, '_') + .replace(/[^a-z0-9_]/g, '') + .replace(/_+/g, '_') + .replace(/^_|_$/g, ''); +} + +function registerEmeFormat(icon) { + if (emeFormat) { + return; + } - /***/ + emeFormat = new ModelFormat(FORMAT_ID, { + name: 'Easy Model Entity', + category: 'minecraft', + description: 'Entity model for the Easy Model Entities mod (Minecraft: Java Edition).', + icon: icon || 'pets', + rotate_cubes: true, + box_uv: true, + optional_box_uv: true, + single_texture: false, + bone_rig: true, + centered_grid: true, + animated_textures: false, + animation_mode: true, + animation_files: false, + java_edition_mapping: false, + model_identifier: true, + locators: true, + codec: Codecs.project, + onActivation() { + if (Project && !Project.model_identifier && Project.name) { + Project.model_identifier = toModelIdentifier(Project.name); + } + }, + onDeactivation() { }, + }); +} - /***/ 16 - (module) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ +function unregisterEmeFormat() { + if (emeFormat) { + emeFormat.delete(); + emeFormat = null; + } +} + +module.exports = {FORMAT_ID, registerEmeFormat, unregisterEmeFormat}; + + +/***/ }, + +/***/ 16 +(module) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ // Descriptive, human-readable labels so users see e.g. "Quadruped (4 legs, // ground, standing)" instead of the cryptic "quadruped_still". Registered with // Blockbench's translation system; falls back to English outside Blockbench. - const EN = { - 'eme.dialog.title': 'Easy Model Entities Export', - 'eme.field.preset': 'Preset', - 'eme.field.namespace': 'Namespace (mod id)', - 'eme.field.profileId': 'Profile ID', - 'eme.field.version': 'Version (optional)', - 'eme.field.targetVersion': 'Minecraft Version', - 'eme.field.exportType': 'Export Type', - 'eme.field.modelType': 'Type', - 'eme.field.customize': 'Customize settings', - 'eme.field.hostEntityType': 'Host Entity Type', - 'eme.field.movementType': 'Movement Type', - 'eme.field.bodyType': 'Body Type', - 'eme.field.width': 'Width', - 'eme.field.height': 'Height', - 'eme.field.eyeHeight': 'Eye Height', - 'eme.field.speed': 'Speed', - 'eme.field.stepHeight': 'Step Height', - 'eme.field.gravity': 'Gravity', - 'eme.field.behaviorMode': 'Behavior Mode', - 'eme.field.maxHealth': 'Max Health', - 'eme.field.movementSpeed': 'Movement Speed', - 'eme.field.followRange': 'Follow Range', - 'eme.field.scale': 'Scale', - 'eme.field.shadowRadius': 'Shadow Radius', - 'eme.field.visibleBoundsWidth': 'Visible Bounds Width', - 'eme.field.visibleBoundsHeight': 'Visible Bounds Height', - 'eme.field.visibleBoundsOffset': 'Visible Bounds Offset', - 'eme.field.animationMode': 'Animation Mode', - 'eme.field.swingSpeed': 'Swing Speed', - 'eme.field.walkSpeedMultiplier': 'Walk Speed Multiplier', - 'eme.section.host': 'Host', - 'eme.section.dimensions': 'Dimensions', - 'eme.section.movement': 'Movement', - 'eme.section.behavior': 'Behavior', - 'eme.section.attributes': 'Attributes', - 'eme.section.rendering': 'Rendering', - 'eme.section.animation': 'Animation', - 'eme.setting.enable_customization': 'Show advanced customization (Easy Model Entities)', - 'eme.setting.enable_experimental': 'Show experimental presets (Easy Model Entities)', - 'eme.preset.custom': 'Custom (manual settings)', - 'eme.preset.static': 'Static (no animation, no movement)', - 'eme.preset.statue': 'Statue (display, no movement)', - 'eme.preset.ticking': 'Ticking (server + client tick)', - 'eme.preset.animated': 'Animated (continuous animation)', - 'eme.preset.animated_randomly': 'Animated randomly (random idle bursts)', - 'eme.preset.humanoid_still': 'Humanoid (2 legs, standing)', - 'eme.preset.humanoid_wandering': 'Humanoid (2 legs, wandering)', - 'eme.preset.quadruped_still': 'Quadruped (4 legs, ground, standing)', - 'eme.preset.quadruped_wandering': 'Quadruped (4 legs, ground, wandering)', - 'eme.preset.aquatic_still': 'Aquatic (fish, still)', - 'eme.preset.aquatic_swimming': 'Aquatic (fish, swimming)', - 'eme.preset.winged_still': 'Winged (bird, perched)', - 'eme.preset.winged_wandering': 'Winged (bird, flying)', - 'eme.preset.winged_humanoid_still': 'Winged humanoid (standing)', - 'eme.preset.winged_humanoid_wandering': 'Winged humanoid (wandering)', - 'eme.preset.arthropod_still': 'Arthropod (insect/spider, still)', - 'eme.preset.arthropod_wandering': 'Arthropod (insect/spider, wandering)', - 'eme.preset.cuboid_still': 'Cuboid (block shape, still)', - 'eme.preset.cuboid_hopping': 'Cuboid (block shape, hopping)', - 'eme.preset.floating_still': 'Floating (hovering, still)', - 'eme.body.static': 'Static', - 'eme.body.biped': 'Biped (2 legs)', - 'eme.body.quadruped': 'Quadruped (4 legs)', - 'eme.body.aquatic': 'Aquatic', - 'eme.body.winged': 'Winged', - 'eme.body.winged_humanoid': 'Winged humanoid', - 'eme.body.arthropod': 'Arthropod', - 'eme.body.cuboid': 'Cuboid', - 'eme.body.floating': 'Floating', - 'eme.movement.ground': 'Ground', - 'eme.movement.static': 'Static', - 'eme.behavior.idle_only': 'Idle only', - 'eme.behavior.ambient': 'Ambient (wanders)', - 'eme.behavior.static': 'Static', - 'eme.behavior.external_owner': 'External owner', - 'eme.animation.automatic': 'Automatic', - 'eme.animation.random_idle': 'Random idle', - 'eme.animation.none': 'None', - 'eme.entity.ground_entity': 'Ground entity', - 'eme.entity.static_entity': 'Static entity', - 'eme.modelType.entity': 'Entity', - 'eme.modelType.block_entity': 'Block Entity', - 'eme.exportType.packs': 'Standalone: Data Pack + Resource Pack (ZIP)', - 'eme.exportType.mod_project': 'Standalone: write into mod project', - 'eme.exportType.model_only': 'Model only: mod integration (no data pack)' - }; - - const DE = { - 'eme.dialog.title': 'Easy Model Entities Export', - 'eme.field.preset': 'Vorlage', - 'eme.field.namespace': 'Namespace (Mod-ID)', - 'eme.field.profileId': 'Profil-ID', - 'eme.field.version': 'Version (optional)', - 'eme.field.targetVersion': 'Minecraft-Version', - 'eme.field.exportType': 'Export-Typ', - 'eme.field.modelType': 'Typ', - 'eme.field.customize': 'Einstellungen anpassen', - 'eme.field.hostEntityType': 'Host-Entität', - 'eme.field.movementType': 'Bewegungsart', - 'eme.field.bodyType': 'Körpertyp', - 'eme.field.width': 'Breite', - 'eme.field.height': 'Höhe', - 'eme.field.eyeHeight': 'Augenhöhe', - 'eme.field.speed': 'Geschwindigkeit', - 'eme.field.stepHeight': 'Schritthöhe', - 'eme.field.gravity': 'Schwerkraft', - 'eme.field.behaviorMode': 'Verhaltensmodus', - 'eme.field.maxHealth': 'Maximale Lebenspunkte', - 'eme.field.movementSpeed': 'Bewegungsgeschwindigkeit', - 'eme.field.followRange': 'Folgereichweite', - 'eme.field.scale': 'Skalierung', - 'eme.field.shadowRadius': 'Schattenradius', - 'eme.field.visibleBoundsWidth': 'Sichtbarkeitsbox-Breite', - 'eme.field.visibleBoundsHeight': 'Sichtbarkeitsbox-Höhe', - 'eme.field.visibleBoundsOffset': 'Sichtbarkeitsbox-Versatz', - 'eme.field.animationMode': 'Animationsmodus', - 'eme.field.swingSpeed': 'Schwunggeschwindigkeit', - 'eme.field.walkSpeedMultiplier': 'Geh-Geschwindigkeitsfaktor', - 'eme.section.host': 'Host', - 'eme.section.dimensions': 'Abmessungen', - 'eme.section.movement': 'Bewegung', - 'eme.section.behavior': 'Verhalten', - 'eme.section.attributes': 'Attribute', - 'eme.section.rendering': 'Darstellung', - 'eme.section.animation': 'Animation', - 'eme.setting.enable_customization': 'Erweiterte Anpassung anzeigen (Easy Model Entities)', - 'eme.setting.enable_experimental': 'Experimentelle Presets anzeigen (Easy Model Entities)', - 'eme.preset.custom': 'Benutzerdefiniert (manuelle Einstellungen)', - 'eme.preset.static': 'Statisch (keine Animation, keine Bewegung)', - 'eme.preset.statue': 'Statue (Anzeige, keine Bewegung)', - 'eme.preset.ticking': 'Tickend (Server- + Client-Tick)', - 'eme.preset.animated': 'Animiert (durchgehende Animation)', - 'eme.preset.animated_randomly': 'Zufällig animiert (zufällige Leerlauf-Schübe)', - 'eme.preset.humanoid_still': 'Humanoid (2 Beine, stehend)', - 'eme.preset.humanoid_wandering': 'Humanoid (2 Beine, umherlaufend)', - 'eme.preset.quadruped_still': 'Vierbeiner (4 Beine, Boden, stehend)', - 'eme.preset.quadruped_wandering': 'Vierbeiner (4 Beine, Boden, umherlaufend)', - 'eme.preset.aquatic_still': 'Wassertier (Fisch, ruhend)', - 'eme.preset.aquatic_swimming': 'Wassertier (Fisch, schwimmend)', - 'eme.preset.winged_still': 'Geflügelt (Vogel, sitzend)', - 'eme.preset.winged_wandering': 'Geflügelt (Vogel, fliegend)', - 'eme.preset.winged_humanoid_still': 'Geflügelter Humanoid (stehend)', - 'eme.preset.winged_humanoid_wandering': 'Geflügelter Humanoid (umherlaufend)', - 'eme.preset.arthropod_still': 'Gliederfüßer (Insekt/Spinne, ruhend)', - 'eme.preset.arthropod_wandering': 'Gliederfüßer (Insekt/Spinne, umherlaufend)', - 'eme.preset.cuboid_still': 'Quaderförmig (blockartig, ruhend)', - 'eme.preset.cuboid_hopping': 'Quaderförmig (blockartig, hüpfend)', - 'eme.preset.floating_still': 'Schwebend (ruhend)', - 'eme.body.static': 'Statisch', - 'eme.body.biped': 'Zweibeiner (2 Beine)', - 'eme.body.quadruped': 'Vierbeiner (4 Beine)', - 'eme.body.aquatic': 'Wassertier', - 'eme.body.winged': 'Geflügelt', - 'eme.body.winged_humanoid': 'Geflügelter Humanoid', - 'eme.body.arthropod': 'Gliederfüßer', - 'eme.body.cuboid': 'Quaderförmig', - 'eme.body.floating': 'Schwebend', - 'eme.movement.ground': 'Boden', - 'eme.movement.static': 'Statisch', - 'eme.behavior.idle_only': 'Nur Leerlauf', - 'eme.behavior.ambient': 'Umgebung (läuft umher)', - 'eme.behavior.static': 'Statisch', - 'eme.behavior.external_owner': 'Externer Besitzer', - 'eme.animation.automatic': 'Automatisch', - 'eme.animation.random_idle': 'Zufälliger Leerlauf', - 'eme.animation.none': 'Keine', - 'eme.entity.ground_entity': 'Boden-Entität', - 'eme.entity.static_entity': 'Statische Entität', - 'eme.modelType.entity': 'Entität', - 'eme.modelType.block_entity': 'Block-Entität', - 'eme.exportType.packs': 'Standalone: Data Pack + Resource Pack (ZIP)', - 'eme.exportType.mod_project': 'Standalone: in Mod-Projekt schreiben', - 'eme.exportType.model_only': 'Nur Modell: Mod-Integration (kein Data Pack)' - }; - - function registerTranslations() { - if (typeof Language !== 'undefined' && typeof Language.addTranslations - === 'function') { - Language.addTranslations('en', EN); - Language.addTranslations('de', DE); - } - } +const EN = { + 'eme.dialog.title': 'Easy Model Entities Export', + 'eme.field.preset': 'Preset', + 'eme.field.namespace': 'Namespace (mod id)', + 'eme.field.profileId': 'Profile ID', + 'eme.field.targetVersion': 'Minecraft Version', + 'eme.field.exportType': 'Export Type', + 'eme.field.modelType': 'Type', + 'eme.field.customize': 'Customize settings', + 'eme.field.hostEntityType': 'Host Entity Type', + 'eme.field.movementType': 'Movement Type', + 'eme.field.bodyType': 'Body Type', + 'eme.field.width': 'Width', + 'eme.field.height': 'Height', + 'eme.field.eyeHeight': 'Eye Height', + 'eme.field.speed': 'Speed', + 'eme.field.stepHeight': 'Step Height', + 'eme.field.gravity': 'Gravity', + 'eme.field.behaviorMode': 'Behavior Mode', + 'eme.field.maxHealth': 'Max Health', + 'eme.field.movementSpeed': 'Movement Speed', + 'eme.field.followRange': 'Follow Range', + 'eme.field.scale': 'Scale', + 'eme.field.shadowRadius': 'Shadow Radius', + 'eme.field.animationMode': 'Animation Mode', + 'eme.field.swingSpeed': 'Swing Speed', + 'eme.field.walkSpeedMultiplier': 'Walk Speed Multiplier', + 'eme.section.host': 'Host', + 'eme.section.dimensions': 'Dimensions', + 'eme.section.movement': 'Movement', + 'eme.section.behavior': 'Behavior', + 'eme.section.attributes': 'Attributes', + 'eme.section.rendering': 'Rendering', + 'eme.section.animation': 'Animation', + 'eme.setting.enable_customization': 'Show advanced customization (Easy Model Entities)', + 'eme.setting.enable_experimental': 'Show experimental presets (Easy Model Entities)', + 'eme.preset.custom': 'Custom (manual settings)', + 'eme.preset.static': 'Static (no animation, no movement)', + 'eme.preset.statue': 'Statue (display, no movement)', + 'eme.preset.ticking': 'Ticking (server + client tick)', + 'eme.preset.animated': 'Animated (continuous animation)', + 'eme.preset.animated_randomly': 'Animated randomly (random idle bursts)', + 'eme.preset.humanoid_still': 'Humanoid (2 legs, standing)', + 'eme.preset.humanoid_wandering': 'Humanoid (2 legs, wandering)', + 'eme.preset.quadruped_still': 'Quadruped (4 legs, ground, standing)', + 'eme.preset.quadruped_wandering': 'Quadruped (4 legs, ground, wandering)', + 'eme.preset.aquatic_still': 'Aquatic (fish, still)', + 'eme.preset.aquatic_swimming': 'Aquatic (fish, swimming)', + 'eme.preset.winged_still': 'Winged (bird, perched)', + 'eme.preset.winged_wandering': 'Winged (bird, flying)', + 'eme.preset.winged_humanoid_still': 'Winged humanoid (standing)', + 'eme.preset.winged_humanoid_wandering': 'Winged humanoid (wandering)', + 'eme.preset.arthropod_still': 'Arthropod (insect/spider, still)', + 'eme.preset.arthropod_wandering': 'Arthropod (insect/spider, wandering)', + 'eme.preset.cuboid_still': 'Cuboid (block shape, still)', + 'eme.preset.cuboid_hopping': 'Cuboid (block shape, hopping)', + 'eme.preset.floating_still': 'Floating (hovering, still)', + 'eme.body.static': 'Static', + 'eme.body.biped': 'Biped (2 legs)', + 'eme.body.quadruped': 'Quadruped (4 legs)', + 'eme.body.aquatic': 'Aquatic', + 'eme.body.winged': 'Winged', + 'eme.body.winged_humanoid': 'Winged humanoid', + 'eme.body.arthropod': 'Arthropod', + 'eme.body.cuboid': 'Cuboid', + 'eme.body.floating': 'Floating', + 'eme.movement.ground': 'Ground', + 'eme.movement.static': 'Static', + 'eme.behavior.idle_only': 'Idle only', + 'eme.behavior.ambient': 'Ambient (wanders)', + 'eme.behavior.static': 'Static', + 'eme.behavior.external_owner': 'External owner', + 'eme.animation.automatic': 'Automatic', + 'eme.animation.random_idle': 'Random idle', + 'eme.animation.none': 'None', + 'eme.entity.ground_entity': 'Ground entity', + 'eme.entity.static_entity': 'Static entity', + 'eme.modelType.entity': 'Entity', + 'eme.modelType.block_entity': 'Block Entity', + 'eme.exportType.packs': 'Standalone: Data Pack + Resource Pack (ZIP)', + 'eme.exportType.mod_project': 'Standalone: write into mod project', + 'eme.exportType.model_only': 'Model only: mod integration (no data pack)' +}; + +const DE = { + 'eme.dialog.title': 'Easy Model Entities Export', + 'eme.field.preset': 'Vorlage', + 'eme.field.namespace': 'Namespace (Mod-ID)', + 'eme.field.profileId': 'Profil-ID', + 'eme.field.targetVersion': 'Minecraft-Version', + 'eme.field.exportType': 'Export-Typ', + 'eme.field.modelType': 'Typ', + 'eme.field.customize': 'Einstellungen anpassen', + 'eme.field.hostEntityType': 'Host-Entität', + 'eme.field.movementType': 'Bewegungsart', + 'eme.field.bodyType': 'Körpertyp', + 'eme.field.width': 'Breite', + 'eme.field.height': 'Höhe', + 'eme.field.eyeHeight': 'Augenhöhe', + 'eme.field.speed': 'Geschwindigkeit', + 'eme.field.stepHeight': 'Schritthöhe', + 'eme.field.gravity': 'Schwerkraft', + 'eme.field.behaviorMode': 'Verhaltensmodus', + 'eme.field.maxHealth': 'Maximale Lebenspunkte', + 'eme.field.movementSpeed': 'Bewegungsgeschwindigkeit', + 'eme.field.followRange': 'Folgereichweite', + 'eme.field.scale': 'Skalierung', + 'eme.field.shadowRadius': 'Schattenradius', + 'eme.field.animationMode': 'Animationsmodus', + 'eme.field.swingSpeed': 'Schwunggeschwindigkeit', + 'eme.field.walkSpeedMultiplier': 'Geh-Geschwindigkeitsfaktor', + 'eme.section.host': 'Host', + 'eme.section.dimensions': 'Abmessungen', + 'eme.section.movement': 'Bewegung', + 'eme.section.behavior': 'Verhalten', + 'eme.section.attributes': 'Attribute', + 'eme.section.rendering': 'Darstellung', + 'eme.section.animation': 'Animation', + 'eme.setting.enable_customization': 'Erweiterte Anpassung anzeigen (Easy Model Entities)', + 'eme.setting.enable_experimental': 'Experimentelle Presets anzeigen (Easy Model Entities)', + 'eme.preset.custom': 'Benutzerdefiniert (manuelle Einstellungen)', + 'eme.preset.static': 'Statisch (keine Animation, keine Bewegung)', + 'eme.preset.statue': 'Statue (Anzeige, keine Bewegung)', + 'eme.preset.ticking': 'Tickend (Server- + Client-Tick)', + 'eme.preset.animated': 'Animiert (durchgehende Animation)', + 'eme.preset.animated_randomly': 'Zufällig animiert (zufällige Leerlauf-Schübe)', + 'eme.preset.humanoid_still': 'Humanoid (2 Beine, stehend)', + 'eme.preset.humanoid_wandering': 'Humanoid (2 Beine, umherlaufend)', + 'eme.preset.quadruped_still': 'Vierbeiner (4 Beine, Boden, stehend)', + 'eme.preset.quadruped_wandering': 'Vierbeiner (4 Beine, Boden, umherlaufend)', + 'eme.preset.aquatic_still': 'Wassertier (Fisch, ruhend)', + 'eme.preset.aquatic_swimming': 'Wassertier (Fisch, schwimmend)', + 'eme.preset.winged_still': 'Geflügelt (Vogel, sitzend)', + 'eme.preset.winged_wandering': 'Geflügelt (Vogel, fliegend)', + 'eme.preset.winged_humanoid_still': 'Geflügelter Humanoid (stehend)', + 'eme.preset.winged_humanoid_wandering': 'Geflügelter Humanoid (umherlaufend)', + 'eme.preset.arthropod_still': 'Gliederfüßer (Insekt/Spinne, ruhend)', + 'eme.preset.arthropod_wandering': 'Gliederfüßer (Insekt/Spinne, umherlaufend)', + 'eme.preset.cuboid_still': 'Quaderförmig (blockartig, ruhend)', + 'eme.preset.cuboid_hopping': 'Quaderförmig (blockartig, hüpfend)', + 'eme.preset.floating_still': 'Schwebend (ruhend)', + 'eme.body.static': 'Statisch', + 'eme.body.biped': 'Zweibeiner (2 Beine)', + 'eme.body.quadruped': 'Vierbeiner (4 Beine)', + 'eme.body.aquatic': 'Wassertier', + 'eme.body.winged': 'Geflügelt', + 'eme.body.winged_humanoid': 'Geflügelter Humanoid', + 'eme.body.arthropod': 'Gliederfüßer', + 'eme.body.cuboid': 'Quaderförmig', + 'eme.body.floating': 'Schwebend', + 'eme.movement.ground': 'Boden', + 'eme.movement.static': 'Statisch', + 'eme.behavior.idle_only': 'Nur Leerlauf', + 'eme.behavior.ambient': 'Umgebung (läuft umher)', + 'eme.behavior.static': 'Statisch', + 'eme.behavior.external_owner': 'Externer Besitzer', + 'eme.animation.automatic': 'Automatisch', + 'eme.animation.random_idle': 'Zufälliger Leerlauf', + 'eme.animation.none': 'Keine', + 'eme.entity.ground_entity': 'Boden-Entität', + 'eme.entity.static_entity': 'Statische Entität', + 'eme.modelType.entity': 'Entität', + 'eme.modelType.block_entity': 'Block-Entität', + 'eme.exportType.packs': 'Standalone: Data Pack + Resource Pack (ZIP)', + 'eme.exportType.mod_project': 'Standalone: in Mod-Projekt schreiben', + 'eme.exportType.model_only': 'Nur Modell: Mod-Integration (kein Data Pack)' +}; + +function registerTranslations() { + if (typeof Language !== 'undefined' && typeof Language.addTranslations + === 'function') { + Language.addTranslations('en', EN); + Language.addTranslations('de', DE); + } +} // Resolves a key via Blockbench's tl() when available, otherwise English. - function t(key) { - if (typeof tl === 'function') { - const translated = tl(key); - if (translated && translated !== key) { - return translated; - } - } - return EN[key] || key; - } - - module.exports = {EN, DE, registerTranslations, t}; - - /***/ - }, - - /***/ 763 - (module) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - class ModelDimensions { - static PIXELS_PER_BLOCK = 16; - static FALLBACK = { - width: 0.6, - height: 0.8, - eyeHeight: 0.5, - visibleBoundsWidth: 1.2, - visibleBoundsHeight: 1, - visibleBoundsOffset: [0, 0.5, 0] - }; - - static #EYE_HEIGHT_FACTOR = {biped: 0.9, quadruped: 0.8, static: 0.85}; - static #BOUNDS_MARGIN = 1.1; - - static #round(value, digits) { - const factor = Math.pow(10, digits == null ? 3 : digits); - return Math.round(value * factor) / factor; - } +function t(key) { + if (typeof tl === 'function') { + const translated = tl(key); + if (translated && translated !== key) { + return translated; + } + } - static #isUsableBounds(bounds) { - if (!bounds) { - return false; - } + return EN[key] || key; +} + +module.exports = {EN, DE, registerTranslations, t}; + + +/***/ }, + +/***/ 763 +(module) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +class ModelDimensions { + static PIXELS_PER_BLOCK = 16; + static FALLBACK = { + width: 0.6, + height: 0.8, + eyeHeight: 0.5 + }; + + static #EYE_HEIGHT_FACTOR = {biped: 0.9, quadruped: 0.8, static: 0.85}; + + static #round(value, digits) { + const factor = Math.pow(10, digits == null ? 3 : digits); + return Math.round(value * factor) / factor; + } - return ['minX', 'minY', 'minZ', 'maxX', 'maxY', 'maxZ'].every( - (key) => Number.isFinite(bounds[key])); - } + static #isUsableBounds(bounds) { + if (!bounds) { + return false; + } - static deriveDimensions(bounds, bodyType) { - if (!ModelDimensions.#isUsableBounds(bounds)) { - return { - ...ModelDimensions.FALLBACK, - visibleBoundsOffset: ModelDimensions.FALLBACK.visibleBoundsOffset.slice() - }; - } + return ['minX', 'minY', 'minZ', 'maxX', 'maxY', 'maxZ'].every( + (key) => Number.isFinite(bounds[key])); + } - const extentX = Math.max(0, bounds.maxX - bounds.minX) - / ModelDimensions.PIXELS_PER_BLOCK; - const extentY = Math.max(0, bounds.maxY - bounds.minY) - / ModelDimensions.PIXELS_PER_BLOCK; - const extentZ = Math.max(0, bounds.maxZ - bounds.minZ) - / ModelDimensions.PIXELS_PER_BLOCK; - - const width = Math.max(extentX, extentZ); - const height = extentY; - if (width <= 0 || height <= 0) { - return { - ...ModelDimensions.FALLBACK, - visibleBoundsOffset: ModelDimensions.FALLBACK.visibleBoundsOffset.slice() - }; - } + static deriveDimensions(bounds, bodyType) { + if (!ModelDimensions.#isUsableBounds(bounds)) { + return {...ModelDimensions.FALLBACK}; + } - const eyeHeightFactor = - ModelDimensions.#EYE_HEIGHT_FACTOR[bodyType] == null - ? ModelDimensions.#EYE_HEIGHT_FACTOR.static - : ModelDimensions.#EYE_HEIGHT_FACTOR[bodyType]; - - return { - width: ModelDimensions.#round(width), - height: ModelDimensions.#round(height), - eyeHeight: ModelDimensions.#round(height * eyeHeightFactor), - visibleBoundsWidth: ModelDimensions.#round( - width * ModelDimensions.#BOUNDS_MARGIN), - visibleBoundsHeight: ModelDimensions.#round( - height * ModelDimensions.#BOUNDS_MARGIN), - visibleBoundsOffset: [0, ModelDimensions.#round(height / 2), 0] - }; - } + const width = Math.max( + Math.max(0, bounds.maxX - bounds.minX), + Math.max(0, bounds.maxZ - bounds.minZ) + ) / ModelDimensions.PIXELS_PER_BLOCK; + const height = Math.max(0, bounds.maxY - bounds.minY) + / ModelDimensions.PIXELS_PER_BLOCK; - // mutates settings in-place and returns it - static applyModelDimensions(settings, modelDimensions) { - if (!modelDimensions) { - return settings; - } + if (width <= 0 || height <= 0) { + return {...ModelDimensions.FALLBACK}; + } - settings.dimensions.width = modelDimensions.width; - settings.dimensions.height = modelDimensions.height; - settings.dimensions.eyeHeight = modelDimensions.eyeHeight; - settings.rendering.visibleBoundsWidth = modelDimensions.visibleBoundsWidth; - settings.rendering.visibleBoundsHeight = modelDimensions.visibleBoundsHeight; - settings.rendering.visibleBoundsOffset = modelDimensions.visibleBoundsOffset.slice(); - return settings; - } - } + const eyeHeightFactor = ModelDimensions.#EYE_HEIGHT_FACTOR[bodyType] + ?? ModelDimensions.#EYE_HEIGHT_FACTOR.static; - module.exports = {ModelDimensions}; + return { + width: ModelDimensions.#round(width), + height: ModelDimensions.#round(height), + eyeHeight: ModelDimensions.#round(height * eyeHeightFactor) + }; + } - /***/ - }, + // mutates settings in-place and returns it + static applyModelDimensions(settings, modelDimensions) { + if (!modelDimensions) { + return settings; + } - /***/ 858 - (module) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ + settings.dimensions.width = modelDimensions.width; + settings.dimensions.height = modelDimensions.height; + settings.dimensions.eyeHeight = modelDimensions.eyeHeight; + return settings; + } +} + +module.exports = {ModelDimensions}; + + +/***/ }, + +/***/ 858 +(module) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ // Suggests the most likely ModelPresetType from a model's bone names and bounds. // Pure and side-effect free so it can be unit tested in isolation. Always // suggests a "still" variant (the safe default); the user can switch to a // moving variant in the dialog. - class PresetDetector { - static #CUBE_RATIO = 1.4; +class PresetDetector { + static #CUBE_RATIO = 1.4; - static #names(stats) { - const boneNames = (stats && stats.boneNames) || []; - return boneNames.map((name) => String(name).toLowerCase()); - } + static #names(stats) { + const boneNames = (stats && stats.boneNames) || []; + return boneNames.map((name) => String(name).toLowerCase()); + } - static #any(names, ...needles) { - return names.some( - (name) => needles.some((needle) => name.includes(needle))); - } + static #any(names, ...needles) { + return names.some( + (name) => needles.some((needle) => name.includes(needle))); + } - static #count(names, needle) { - return names.filter((name) => name.includes(needle)).length; - } + static #count(names, needle) { + return names.filter((name) => name.includes(needle)).length; + } - static #extents(bounds) { - if (!bounds) { - return null; - } - const keys = ['minX', 'minY', 'minZ', 'maxX', 'maxY', 'maxZ']; - if (!keys.every((key) => Number.isFinite(bounds[key]))) { - return null; - } - return { - x: Math.max(0, bounds.maxX - bounds.minX), - y: Math.max(0, bounds.maxY - bounds.minY), - z: Math.max(0, bounds.maxZ - bounds.minZ) - }; - } + static #extents(bounds) { + if (!bounds) { + return null; + } - // Returns {presetType, reason}. - static detect(stats, bounds) { - const names = PresetDetector.#names(stats); - const hasWings = PresetDetector.#any(names, 'wing'); - const hasArms = PresetDetector.#any(names, 'arm'); - const legCount = PresetDetector.#count(names, 'leg'); - const hasFrontBackLegs = names.some( - (name) => name.includes('leg') - && (name.includes('front') || name.includes('back'))); - - if (hasWings) { - return hasArms - ? {presetType: 'winged_humanoid_still', reason: 'wings + arms'} - : {presetType: 'winged_still', reason: 'wing bones'}; - } + if (!['minX', 'minY', 'minZ', 'maxX', 'maxY', 'maxZ'].every( + (key) => Number.isFinite(bounds[key]))) { + return null; + } - if (PresetDetector.#any(names, 'fin', 'fluke', 'fish')) { - return {presetType: 'aquatic_still', reason: 'fin/fish bones'}; - } + return { + x: Math.max(0, bounds.maxX - bounds.minX), + y: Math.max(0, bounds.maxY - bounds.minY), + z: Math.max(0, bounds.maxZ - bounds.minZ) + }; + } - if (legCount >= 6 || PresetDetector.#any(names, 'spider', 'insect', - 'mandible')) { - return {presetType: 'arthropod_still', reason: 'six or more legs'}; - } + static detect(stats, bounds) { + const names = PresetDetector.#names(stats); + const hasWings = PresetDetector.#any(names, 'wing'); + const hasArms = PresetDetector.#any(names, 'arm'); + const legCount = PresetDetector.#count(names, 'leg'); + const hasFrontBackLegs = names.some( + (name) => name.includes('leg') + && (name.includes('front') || name.includes('back'))); + + if (hasWings) { + return hasArms + ? {presetType: 'winged_humanoid_still', reason: 'wings + arms'} + : {presetType: 'winged_still', reason: 'wing bones'}; + } - if (hasFrontBackLegs || legCount === 4) { - return { - presetType: 'quadruped_still', - reason: 'four legs (front/back)' - }; - } + if (PresetDetector.#any(names, 'fin', 'fluke', 'fish')) { + return {presetType: 'aquatic_still', reason: 'fin/fish bones'}; + } - if (hasArms) { - return {presetType: 'humanoid_still', reason: 'arm bones'}; - } + if (legCount >= 6 || PresetDetector.#any(names, 'spider', 'insect', + 'mandible')) { + return {presetType: 'arthropod_still', reason: 'six or more legs'}; + } - const extents = PresetDetector.#extents(bounds); - if (extents && extents.x > 0 && extents.y > 0 && extents.z > 0) { - const max = Math.max(extents.x, extents.y, extents.z); - const min = Math.min(extents.x, extents.y, extents.z); - if (max / min <= PresetDetector.#CUBE_RATIO) { - return { - presetType: 'cuboid_still', - reason: 'cube-like proportions' - }; - } - if (extents.y < Math.max(extents.x, extents.z) * 0.5) { - return {presetType: 'aquatic_still', reason: 'flat proportions'}; - } - } + if (hasFrontBackLegs || legCount === 4) { + return {presetType: 'quadruped_still', reason: 'four legs (front/back)'}; + } - return {presetType: 'statue', reason: 'no distinguishing limbs'}; - } + if (hasArms) { + return {presetType: 'humanoid_still', reason: 'arm bones'}; + } - static detectPresetType(stats, bounds) { - return PresetDetector.detect(stats, bounds).presetType; - } + const extents = PresetDetector.#extents(bounds); + if (extents && extents.x > 0 && extents.y > 0 && extents.z > 0) { + if (Math.max(extents.x, extents.y, extents.z) / Math.min(extents.x, + extents.y, extents.z) <= PresetDetector.#CUBE_RATIO) { + return {presetType: 'cuboid_still', reason: 'cube-like proportions'}; + } + if (extents.y < Math.max(extents.x, extents.z) * 0.5) { + return {presetType: 'aquatic_still', reason: 'flat proportions'}; } + } - module.exports = {PresetDetector}; + return {presetType: 'statue', reason: 'no distinguishing limbs'}; + } - /***/ - }, + static detectPresetType(stats, bounds) { + return PresetDetector.detect(stats, bounds).presetType; + } +} + +module.exports = {PresetDetector}; + + +/***/ }, + +/***/ 229 +(module, __unused_webpack_exports, __webpack_require__) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {ResourceLocation} = __webpack_require__(20); +const { + PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + MODEL_TYPE_BLOCK_ENTITY +} = __webpack_require__(151); + +class Validator { + static BUDGETS = { + maxTextureSize: 2048, + maxModelFileSize: 2 * 1024 * 1024, + maxCubeCount: 512, + maxBoneCount: 128, + maxHierarchyDepth: 32 + }; + + static REQUIRED_BODY_PARTS = { + quadruped: ['root', 'body', 'head', 'front_left_leg', 'front_right_leg', + 'back_left_leg', 'back_right_leg'], + biped: ['root', 'head', 'body', 'left_arm', 'right_arm', 'left_leg', + 'right_leg'], + static: [] + }; + + static #isFiniteNumber(value) { + return typeof value === 'number' && Number.isFinite(value); + } - /***/ 229 - (module, __unused_webpack_exports, __webpack_require__) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - const {ResourceLocation} = __webpack_require__(20); - const { - PRESET_TYPES, - BLOCK_ENTITY_PRESET_TYPES, - MODEL_TYPE_BLOCK_ENTITY - } = __webpack_require__(151); - - class Validator { - static BUDGETS = { - maxTextureSize: 2048, - maxModelFileSize: 2 * 1024 * 1024, - maxCubeCount: 512, - maxBoneCount: 128, - maxHierarchyDepth: 32 - }; - - static REQUIRED_BODY_PARTS = { - quadruped: ['root', 'body', 'head', 'front_left_leg', - 'front_right_leg', - 'back_left_leg', 'back_right_leg'], - biped: ['root', 'head', 'body', 'left_arm', 'right_arm', 'left_leg', - 'right_leg'], - static: [] - }; - - static #isFiniteNumber(value) { - return typeof value === 'number' && Number.isFinite(value); - } + static #validateNumeric(errors, label, value) { + if (!Validator.#isFiniteNumber(value)) { + errors.push({ + code: 'INVALID_NUMERIC', + message: `${label} must be a finite number` + }); + return false; + } - static #validateNumeric(errors, label, value) { - if (!Validator.#isFiniteNumber(value)) { - errors.push({ - code: 'INVALID_NUMERIC', - message: `${label} must be a finite number` - }); - return false; - } - return true; - } + return true; + } - static validateSettings(settings, context) { - const errors = []; - const warnings = []; - const ctx = context || {}; + static validateSettings(settings, context) { + const errors = []; + const warnings = []; + const ctx = context || {}; - if (!ResourceLocation.isValidNamespace(settings.namespace)) { - errors.push({ - code: 'INVALID_NAMESPACE', - message: `Invalid namespace: ${settings.namespace}` - }); - } - if (!ResourceLocation.isValidPath(settings.profileId)) { - errors.push({ - code: 'INVALID_PROFILE_ID', - message: `Invalid profile ID: ${settings.profileId}` - }); - } + if (!ResourceLocation.isValidNamespace(settings.namespace)) { + errors.push({ + code: 'INVALID_NAMESPACE', + message: `Invalid namespace: ${settings.namespace}` + }); + } + if (!ResourceLocation.isValidPath(settings.profileId)) { + errors.push({ + code: 'INVALID_PROFILE_ID', + message: `Invalid profile ID: ${settings.profileId}` + }); + } - const id = `${settings.namespace}:${settings.profileId}`; - if ( - ResourceLocation.isValidNamespace(settings.namespace) && - ResourceLocation.isValidPath(settings.profileId) && - !ResourceLocation.isValidResourceLocation(id) - ) { - errors.push({ - code: 'INVALID_RESOURCE_LOCATION', - message: `Invalid resource location: ${id}` - }); - } - const blockEntity = settings.modelType === MODEL_TYPE_BLOCK_ENTITY; - - // Block entity host type is derived by the mod; only entities carry one. - if (!blockEntity - && !ResourceLocation.isValidResourceLocation( - settings.host.entityType)) { - errors.push({ - code: 'INVALID_RESOURCE_LOCATION', - message: `Invalid host entity type: ${settings.host.entityType}` - }); - } - const validPresets = blockEntity ? BLOCK_ENTITY_PRESET_TYPES - : PRESET_TYPES; - if (!validPresets.includes(settings.presetType)) { - errors.push({ - code: 'INVALID_PRESET_TYPE', - message: `Invalid preset type: ${settings.presetType}` - }); - } + const id = `${settings.namespace}:${settings.profileId}`; + if ( + ResourceLocation.isValidNamespace(settings.namespace) && + ResourceLocation.isValidPath(settings.profileId) && + !ResourceLocation.isValidResourceLocation(id) + ) { + errors.push({ + code: 'INVALID_RESOURCE_LOCATION', + message: `Invalid resource location: ${id}` + }); + } + const blockEntity = settings.modelType === MODEL_TYPE_BLOCK_ENTITY; + + // Block entity host type is derived by the mod; only entities carry one. + if (!blockEntity + && !ResourceLocation.isValidResourceLocation( + settings.host.entityType)) { + errors.push({ + code: 'INVALID_RESOURCE_LOCATION', + message: `Invalid host entity type: ${settings.host.entityType}` + }); + } + const validPresets = blockEntity ? BLOCK_ENTITY_PRESET_TYPES : PRESET_TYPES; + if (!validPresets.includes(settings.presetType)) { + errors.push({ + code: 'INVALID_PRESET_TYPE', + message: `Invalid preset type: ${settings.presetType}` + }); + } - const {width, height, eyeHeight} = settings.dimensions; - const widthValid = Validator.#validateNumeric(errors, - 'dimensions.width', - width); - const heightValid = Validator.#validateNumeric(errors, - 'dimensions.height', - height); - const eyeValid = Validator.#validateNumeric(errors, - 'dimensions.eye_height', - eyeHeight); - if (widthValid && width <= 0) { - errors.push({ - code: 'INVALID_DIMENSIONS', - message: 'width must be greater than 0' - }); - } - if (heightValid && height <= 0) { - errors.push({ - code: 'INVALID_DIMENSIONS', - message: 'height must be greater than 0' - }); - } - if (eyeValid && eyeHeight < 0) { - errors.push({ - code: 'INVALID_DIMENSIONS', - message: 'eye_height must not be negative' - }); - } - if (eyeValid && heightValid && eyeHeight > height) { - errors.push({ - code: 'INVALID_DIMENSIONS', - message: 'eye_height must not exceed height' - }); - } + const {width, height, eyeHeight} = settings.dimensions; + const widthValid = Validator.#validateNumeric(errors, 'dimensions.width', + width); + const heightValid = Validator.#validateNumeric(errors, 'dimensions.height', + height); + const eyeValid = Validator.#validateNumeric(errors, 'dimensions.eye_height', + eyeHeight); + if (widthValid && width <= 0) { + errors.push({ + code: 'INVALID_DIMENSIONS', + message: 'width must be greater than 0' + }); + } + if (heightValid && height <= 0) { + errors.push({ + code: 'INVALID_DIMENSIONS', + message: 'height must be greater than 0' + }); + } + if (eyeValid && eyeHeight < 0) { + errors.push({ + code: 'INVALID_DIMENSIONS', + message: 'eye_height must not be negative' + }); + } + if (eyeValid && heightValid && eyeHeight > height) { + errors.push({ + code: 'INVALID_DIMENSIONS', + message: 'eye_height must not exceed height' + }); + } - Validator.#validateNumeric(errors, 'movement.speed', - settings.movement.speed); - Validator.#validateNumeric(errors, 'movement.step_height', - settings.movement.stepHeight); - - if (ctx.hasModel === false) { - errors.push( - { - code: 'MISSING_MODEL', - message: 'No model present in project' - }); - } - if (ctx.hasTexture === false) { - errors.push( - { - code: 'MISSING_TEXTURE', - message: 'No texture present in project' - }); - } + Validator.#validateNumeric(errors, 'movement.speed', + settings.movement.speed); + Validator.#validateNumeric(errors, 'movement.step_height', + settings.movement.stepHeight); - if (Validator.#isFiniteNumber(ctx.textureCount) && ctx.textureCount - > 1) { - warnings.push({ - code: 'MULTIPLE_TEXTURES', - message: 'Project has multiple textures; only the active texture is exported' - }); - } + if (ctx.hasModel === false) { + errors.push( + {code: 'MISSING_MODEL', message: 'No model present in project'}); + } + if (ctx.hasTexture === false) { + errors.push( + {code: 'MISSING_TEXTURE', message: 'No texture present in project'}); + } - if (Validator.#isFiniteNumber(ctx.textureWidth) - && Validator.#isFiniteNumber(ctx.textureHeight)) { - if (ctx.textureWidth > Validator.BUDGETS.maxTextureSize - || ctx.textureHeight > Validator.BUDGETS.maxTextureSize) { - warnings.push({ - code: 'LARGE_TEXTURE', - message: `Texture larger than ${Validator.BUDGETS.maxTextureSize}x${Validator.BUDGETS.maxTextureSize}` - }); - } - } - if (Validator.#isFiniteNumber(ctx.modelFileSize) && ctx.modelFileSize - > Validator.BUDGETS.maxModelFileSize) { - warnings.push( - {code: 'LARGE_MODEL', message: 'Model file larger than 2 MB'}); - } - if (Validator.#isFiniteNumber(ctx.cubeCount) && ctx.cubeCount - > Validator.BUDGETS.maxCubeCount) { - warnings.push({ - code: 'HIGH_CUBE_COUNT', - message: `More than ${Validator.BUDGETS.maxCubeCount} cubes` - }); - } - if (Validator.#isFiniteNumber(ctx.boneCount) && ctx.boneCount - > Validator.BUDGETS.maxBoneCount) { - warnings.push({ - code: 'HIGH_BONE_COUNT', - message: `More than ${Validator.BUDGETS.maxBoneCount} bones` - }); - } - if (Validator.#isFiniteNumber(ctx.hierarchyDepth) - && ctx.hierarchyDepth - > Validator.BUDGETS.maxHierarchyDepth) { - warnings.push({ - code: 'DEEP_HIERARCHY', - message: `Hierarchy deeper than ${Validator.BUDGETS.maxHierarchyDepth}` - }); - } + if (Validator.#isFiniteNumber(ctx.textureWidth) + && Validator.#isFiniteNumber(ctx.textureHeight)) { + if (ctx.textureWidth > Validator.BUDGETS.maxTextureSize + || ctx.textureHeight > Validator.BUDGETS.maxTextureSize) { + warnings.push({ + code: 'LARGE_TEXTURE', + message: `Texture larger than ${Validator.BUDGETS.maxTextureSize}x${Validator.BUDGETS.maxTextureSize}` + }); + } + } + if (Validator.#isFiniteNumber(ctx.modelFileSize) && ctx.modelFileSize + > Validator.BUDGETS.maxModelFileSize) { + warnings.push( + {code: 'LARGE_MODEL', message: 'Model file larger than 2 MB'}); + } + if (Validator.#isFiniteNumber(ctx.cubeCount) && ctx.cubeCount + > Validator.BUDGETS.maxCubeCount) { + warnings.push({ + code: 'HIGH_CUBE_COUNT', + message: `More than ${Validator.BUDGETS.maxCubeCount} cubes` + }); + } + if (Validator.#isFiniteNumber(ctx.boneCount) && ctx.boneCount + > Validator.BUDGETS.maxBoneCount) { + warnings.push({ + code: 'HIGH_BONE_COUNT', + message: `More than ${Validator.BUDGETS.maxBoneCount} bones` + }); + } + if (Validator.#isFiniteNumber(ctx.hierarchyDepth) && ctx.hierarchyDepth + > Validator.BUDGETS.maxHierarchyDepth) { + warnings.push({ + code: 'DEEP_HIERARCHY', + message: `Hierarchy deeper than ${Validator.BUDGETS.maxHierarchyDepth}` + }); + } - const missingParts = Validator.getMissingBodyParts( - settings.host.bodyType, - ctx.boneNames || []); - missingParts.forEach((part) => { - warnings.push({ - code: 'MISSING_BODY_PART', - message: `Missing recommended body part: ${part}` - }); - }); - - if (ctx.visibleBoundsManual === false) { - warnings.push({ - code: 'VISIBLE_BOUNDS_DEFAULT', - message: 'Visible bounds were not set manually' - }); - } + Validator.getMissingBodyParts(settings.host.bodyType, + ctx.boneNames || []).forEach((part) => { + warnings.push({ + code: 'MISSING_BODY_PART', + message: `Missing recommended body part: ${part}` + }); + }); - return {errors, warnings, valid: errors.length === 0}; - } + return {errors, warnings, valid: errors.length === 0}; + } - static getMissingBodyParts(bodyType, boneNames) { - const required = Validator.REQUIRED_BODY_PARTS[bodyType] || []; - const present = new Set( - (boneNames || []).map((name) => String(name).toLowerCase())); - return required.filter((part) => !present.has(part)); - } + static getMissingBodyParts(bodyType, boneNames) { + const required = Validator.REQUIRED_BODY_PARTS[bodyType] || []; + const present = new Set( + (boneNames || []).map((name) => String(name).toLowerCase())); - static validateOutputPath(outputRoot, relativePath) { - if (typeof relativePath !== 'string' || relativePath.length === 0) { - return { - valid: false, - code: 'INVALID_OUTPUT_PATH', - message: 'Empty output path' - }; - } - const normalized = relativePath.replaceAll('\\', '/'); - if (normalized.includes('..')) { - return { - valid: false, - code: 'PATH_TRAVERSAL', - message: 'Path traversal detected' - }; - } - if (/^([a-zA-Z]:\/|\/)/.test(normalized)) { - return { - valid: false, - code: 'ABSOLUTE_PATH', - message: 'Absolute paths are not allowed' - }; - } - return {valid: true, root: outputRoot, path: normalized}; - } - } + return required.filter((part) => !present.has(part)); + } - module.exports = {Validator}; + static validateOutputPath(outputRoot, relativePath) { + if (typeof relativePath !== 'string' || relativePath.length === 0) { + return { + valid: false, + code: 'INVALID_OUTPUT_PATH', + message: 'Empty output path' + }; + } - /***/ - }, + const normalized = relativePath.replaceAll('\\', '/'); + if (normalized.includes('..')) { + return { + valid: false, + code: 'PATH_TRAVERSAL', + message: 'Path traversal detected' + }; + } - /***/ 151 - (module) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ + if (/^([a-zA-Z]:\/|\/)/.test(normalized)) { + return { + valid: false, + code: 'ABSOLUTE_PATH', + message: 'Absolute paths are not allowed' + }; + } + + return {valid: true, root: outputRoot, path: normalized}; + } +} + +module.exports = {Validator}; + + +/***/ }, + +/***/ 151 +(module) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ // Mirrors de.markusbordihn.easymodelentities.profile.ModelPresetType and the // per-preset defaults derived by EasyModelProfileParser / ModelRenderProfileParser. // This file is the single source of truth the plugin uses to compute minimal // (diff-based) profiles, so it must stay in sync with the mod. - const SCHEMA_VERSION = '0.1.0'; +const SCHEMA_VERSION = '0.1.0'; // Mirrors de.markusbordihn.easymodelentities.data.profile.ModelType. - const MODEL_TYPE_ENTITY = 'entity'; - const MODEL_TYPE_BLOCK_ENTITY = 'block_entity'; - const MODEL_TYPES = [MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY]; +const MODEL_TYPE_ENTITY = 'entity'; +const MODEL_TYPE_BLOCK_ENTITY = 'block_entity'; +const MODEL_TYPES = (/* unused pure expression or super */ null && ([MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY])); - const GROUND_ENTITY = 'easy_model_entities:ground_entity'; - const STATIC_ENTITY = 'easy_model_entities:static_entity'; +const GROUND_ENTITY = 'easy_model_entities:ground_entity'; +const STATIC_ENTITY = 'easy_model_entities:static_entity'; - const FALLBACK_DIMENSIONS = {width: 0.6, height: 1.8, eyeHeight: 1.62}; - const BLOCK_ENTITY_DIMENSIONS = {width: 1.0, height: 1.0, eyeHeight: 0.5}; +const FALLBACK_DIMENSIONS = {width: 0.6, height: 1.8, eyeHeight: 1.62}; +const BLOCK_ENTITY_DIMENSIONS = {width: 1.0, height: 1.0, eyeHeight: 0.5}; // All ModelPresetType values in serialized form. - const PRESET_TYPES = [ - 'custom', - 'static', - 'statue', - 'humanoid_still', - 'humanoid_wandering', - 'quadruped_still', - 'quadruped_wandering', - 'aquatic_still', - 'aquatic_swimming', - 'winged_still', - 'winged_wandering', - 'winged_humanoid_still', - 'winged_humanoid_wandering', - 'arthropod_still', - 'arthropod_wandering', - 'cuboid_still', - 'cuboid_hopping', - 'floating_still' - ]; +const PRESET_TYPES = [ + 'custom', + 'static', + 'statue', + 'humanoid_still', + 'humanoid_wandering', + 'quadruped_still', + 'quadruped_wandering', + 'aquatic_still', + 'aquatic_swimming', + 'winged_still', + 'winged_wandering', + 'winged_humanoid_still', + 'winged_humanoid_wandering', + 'arthropod_still', + 'arthropod_wandering', + 'cuboid_still', + 'cuboid_hopping', + 'floating_still' +]; // All ModelBlockEntityPresetType values in serialized form. - const BLOCK_ENTITY_PRESET_TYPES = [ - 'static', - 'ticking', - 'animated', - 'animated_randomly' - ]; +const BLOCK_ENTITY_PRESET_TYPES = [ + 'static', + 'ticking', + 'animated', + 'animated_randomly' +]; // Presets the mod marks as stable for pack authors; experimental ones are hidden // in the UI behind the "Show experimental presets" setting. - const STABLE_PRESET_TYPES = new Set([ - 'static', - 'statue', - 'humanoid_still', - 'humanoid_wandering', - 'quadruped_still', - 'quadruped_wandering', - // Block entity stable presets. - 'animated', - 'animated_randomly' - ]); +const STABLE_PRESET_TYPES = new Set([ + 'static', + 'statue', + 'humanoid_still', + 'humanoid_wandering', + 'quadruped_still', + 'quadruped_wandering', + // Block entity stable presets. + 'animated', + 'animated_randomly' +]); // Presets selectable as a starting point in the UI (custom handled separately). - const SELECTABLE_PRESET_TYPES = PRESET_TYPES.filter( - (id) => id !== 'custom'); - - function isStablePreset(presetType) { - return STABLE_PRESET_TYPES.has(presetType); - } - - function isCustom(presetType) { - return presetType === 'custom'; - } - - function isStill(presetType) { - return String(presetType).endsWith('_still'); - } - - function isMoving(presetType) { - return String(presetType).endsWith('_wandering') - || presetType === 'aquatic_swimming' - || presetType === 'cuboid_hopping'; - } - - function bodyType(presetType) { - switch (presetType) { - case 'humanoid_still': - case 'humanoid_wandering': - return 'biped'; - case 'quadruped_still': - case 'quadruped_wandering': - return 'quadruped'; - case 'aquatic_still': - case 'aquatic_swimming': - return 'aquatic'; - case 'winged_still': - case 'winged_wandering': - return 'winged'; - case 'winged_humanoid_still': - case 'winged_humanoid_wandering': - return 'winged_humanoid'; - case 'arthropod_still': - case 'arthropod_wandering': - return 'arthropod'; - case 'cuboid_still': - case 'cuboid_hopping': - return 'cuboid'; - case 'floating_still': - return 'floating'; - default: - return 'static'; - } - } +const SELECTABLE_PRESET_TYPES = PRESET_TYPES.filter((id) => id !== 'custom'); + +function isStablePreset(presetType) { + return STABLE_PRESET_TYPES.has(presetType); +} + +function isCustom(presetType) { + return presetType === 'custom'; +} + +function isStill(presetType) { + return String(presetType).endsWith('_still'); +} + +function isMoving(presetType) { + return String(presetType).endsWith('_wandering') + || presetType === 'aquatic_swimming' + || presetType === 'cuboid_hopping'; +} + +function bodyType(presetType) { + switch (presetType) { + case 'humanoid_still': + case 'humanoid_wandering': + return 'biped'; + case 'quadruped_still': + case 'quadruped_wandering': + return 'quadruped'; + case 'aquatic_still': + case 'aquatic_swimming': + return 'aquatic'; + case 'winged_still': + case 'winged_wandering': + return 'winged'; + case 'winged_humanoid_still': + case 'winged_humanoid_wandering': + return 'winged_humanoid'; + case 'arthropod_still': + case 'arthropod_wandering': + return 'arthropod'; + case 'cuboid_still': + case 'cuboid_hopping': + return 'cuboid'; + case 'floating_still': + return 'floating'; + default: + return 'static'; + } +} + +function movementType(presetType) { + return isMoving(presetType) ? 'ground' : 'static'; +} + +function entityType(presetType) { + return isMoving(presetType) || presetType === 'static' ? GROUND_ENTITY + : STATIC_ENTITY; +} + +function presetDimensions(presetType) { + switch (presetType) { + case 'quadruped_still': + case 'quadruped_wandering': + return {width: 0.9, height: 0.9, eyeHeight: 0.6}; + case 'aquatic_still': + case 'aquatic_swimming': + return {width: 0.7, height: 0.4, eyeHeight: 0.25}; + case 'winged_still': + case 'winged_wandering': + return {width: 0.6, height: 0.9, eyeHeight: 0.6}; + case 'winged_humanoid_still': + case 'winged_humanoid_wandering': + return {width: 0.6, height: 0.8, eyeHeight: 0.6}; + case 'arthropod_still': + case 'arthropod_wandering': + return {width: 1.4, height: 0.9, eyeHeight: 0.45}; + case 'cuboid_still': + case 'cuboid_hopping': + case 'floating_still': + return {width: 1.0, height: 1.0, eyeHeight: 0.5}; + default: + return {...FALLBACK_DIMENSIONS}; + } +} + +// Shadow radius the mod derives per render preset (ModelRenderSettings). +function presetShadowRadius(presetType) { + switch (presetType) { + case 'quadruped_still': + case 'quadruped_wandering': + return 0.45; + case 'aquatic_still': + case 'aquatic_swimming': + case 'winged_still': + case 'winged_wandering': + case 'winged_humanoid_still': + case 'winged_humanoid_wandering': + return 0.25; + case 'arthropod_still': + case 'arthropod_wandering': + return 0.7; + case 'cuboid_still': + case 'cuboid_hopping': + case 'floating_still': + return 0.5; + default: + return 0.3; + } +} - function movementType(presetType) { - return isMoving(presetType) ? 'ground' : 'static'; - } +// Behavior mode the mod derives for a preset, given the resolved movement type +// (only relevant for the custom preset where movement type can vary). +function behaviorModeFor(presetType, move) { + if (isMoving(presetType)) { + return 'ambient'; + } - function entityType(presetType) { - return isMoving(presetType) || presetType === 'static' ? GROUND_ENTITY - : STATIC_ENTITY; - } + if (isStill(presetType)) { + return 'idle_only'; + } - function presetDimensions(presetType) { - switch (presetType) { - case 'quadruped_still': - case 'quadruped_wandering': - return {width: 0.9, height: 0.9, eyeHeight: 0.6}; - case 'aquatic_still': - case 'aquatic_swimming': - return {width: 0.7, height: 0.4, eyeHeight: 0.25}; - case 'winged_still': - case 'winged_wandering': - return {width: 0.6, height: 0.9, eyeHeight: 0.6}; - case 'winged_humanoid_still': - case 'winged_humanoid_wandering': - return {width: 0.6, height: 0.8, eyeHeight: 0.6}; - case 'arthropod_still': - case 'arthropod_wandering': - return {width: 1.4, height: 0.9, eyeHeight: 0.45}; - case 'cuboid_still': - case 'cuboid_hopping': - case 'floating_still': - return {width: 1.0, height: 1.0, eyeHeight: 0.5}; - default: - return {...FALLBACK_DIMENSIONS}; - } - } + if (presetType === 'custom') { + return move === 'ground' ? 'idle_only' : 'static'; + } -// [visibleBoundsWidth, visibleBoundsHeight, visibleBoundsOffsetY, shadowRadius] - function presetRenderBounds(presetType) { - switch (presetType) { - case 'quadruped_still': - case 'quadruped_wandering': - return [0.9, 0.9, 0.45, 0.45]; - case 'aquatic_still': - case 'aquatic_swimming': - return [0.7, 0.4, 0.2, 0.25]; - case 'winged_still': - case 'winged_wandering': - return [0.6, 0.9, 0.45, 0.25]; - case 'winged_humanoid_still': - case 'winged_humanoid_wandering': - return [0.6, 0.8, 0.4, 0.25]; - case 'arthropod_still': - case 'arthropod_wandering': - return [1.4, 0.9, 0.45, 0.7]; - case 'cuboid_still': - case 'cuboid_hopping': - case 'floating_still': - return [1.0, 1.0, 0.5, 0.5]; - default: - return [0.6, 1.8, 0.9, 0.3]; - } - } + return 'static'; +} -// Behavior mode the mod derives for a preset, given the resolved movement type -// (only relevant for the custom preset where movement type can vary). - function behaviorModeFor(presetType, move) { - if (isMoving(presetType)) { - return 'ambient'; - } - if (isStill(presetType)) { - return 'idle_only'; - } - if (presetType === 'custom') { - return move === 'ground' ? 'idle_only' : 'static'; - } - return 'static'; - } - - function behaviorMode(presetType) { - return behaviorModeFor(presetType, movementType(presetType)); - } +function behaviorMode(presetType) { + return behaviorModeFor(presetType, movementType(presetType)); +} // Movement defaults the mod derives from the resolved movement type. - function movementDefaults(presetType, move) { - const ground = move === 'ground'; - return { - speed: ground ? 0.22 : 0, - stepHeight: ground ? 0.6 : 0, - gravity: presetType === 'static' || ground - }; - } - - function animationMode(presetType) { - return presetType === 'custom' || presetType === 'static' - || presetType === 'statue' ? 'none' : 'automatic'; - } +function movementDefaults(presetType, move) { + const ground = move === 'ground'; + return { + speed: ground ? 0.22 : 0, + stepHeight: ground ? 0.6 : 0, + gravity: presetType === 'static' || ground + }; +} + +function animationMode(presetType) { + return presetType === 'custom' || presetType === 'static' + || presetType === 'statue' ? 'none' : 'automatic'; +} // Animation mode the mod derives for a block entity preset. - function blockEntityAnimationMode(presetType) { - switch (presetType) { - case 'animated': - return 'automatic'; - case 'animated_randomly': - return 'random_idle'; - default: - return 'none'; - } - } +function blockEntityAnimationMode(presetType) { + switch (presetType) { + case 'animated': + return 'automatic'; + case 'animated_randomly': + return 'random_idle'; + default: + return 'none'; + } +} // Block entities ignore movement/behavior/attributes; the host is always a -// static cuboid block. Render bounds default to a 1x1x1 block. - function blockEntityPresetDefaults(presetType) { - return { - schemaVersion: SCHEMA_VERSION, - modelType: MODEL_TYPE_BLOCK_ENTITY, - presetType: presetType, - version: '', - host: {entityType: '', movementType: 'static', bodyType: 'static'}, - dimensions: {...BLOCK_ENTITY_DIMENSIONS}, - movement: {speed: 0, stepHeight: 0, gravity: false}, - behavior: {mode: 'static', lookAtPlayers: false, randomStroll: false}, - attributes: {maxHealth: 10, movementSpeed: 0, followRange: 16}, - rendering: { - scale: 1, - shadowRadius: 0.5, - visibleBoundsWidth: 1.0, - visibleBoundsHeight: 1.0, - visibleBoundsOffset: [0, 0.5, 0] - }, - animation: { - mode: blockEntityAnimationMode(presetType), - swingSpeed: 1, - walkSpeedMultiplier: 1 - } - }; - } - -// Full settings object matching the mod defaults for the given preset type. - function presetDefaults(presetType, modelType) { - if (modelType === MODEL_TYPE_BLOCK_ENTITY) { - return blockEntityPresetDefaults(presetType); - } - const move = movementType(presetType); - const ground = move === 'ground'; - const speed = ground ? 0.22 : 0; - const stepHeight = ground ? 0.6 : 0; - const gravity = presetType === 'static' || ground; - const mode = behaviorMode(presetType); - const lookAtPlayers = mode === 'idle_only' || mode === 'ambient'; - const randomStroll = ground && mode === 'ambient'; - const dimensions = presetDimensions(presetType); - const [boundsWidth, boundsHeight, boundsOffsetY, shadowRadius] = - presetRenderBounds(presetType); - - return { - schemaVersion: SCHEMA_VERSION, - modelType: MODEL_TYPE_ENTITY, - presetType: presetType, - version: '', - host: { - entityType: entityType(presetType), - movementType: move, - bodyType: bodyType(presetType) - }, - dimensions: dimensions, - movement: {speed: speed, stepHeight: stepHeight, gravity: gravity}, - behavior: { - mode: mode, - lookAtPlayers: lookAtPlayers, - randomStroll: randomStroll - }, - attributes: {maxHealth: 10, movementSpeed: speed, followRange: 16}, - rendering: { - scale: 1, - shadowRadius: shadowRadius, - visibleBoundsWidth: boundsWidth, - visibleBoundsHeight: boundsHeight, - visibleBoundsOffset: [0, boundsOffsetY, 0] - }, - animation: { - mode: animationMode(presetType), - swingSpeed: 1, - walkSpeedMultiplier: 1 - } - }; - } +// static cuboid block. +function blockEntityPresetDefaults(presetType) { + return { + schemaVersion: SCHEMA_VERSION, + modelType: MODEL_TYPE_BLOCK_ENTITY, + presetType: presetType, + host: {entityType: '', movementType: 'static', bodyType: 'static'}, + dimensions: {...BLOCK_ENTITY_DIMENSIONS}, + movement: {speed: 0, stepHeight: 0, gravity: false}, + behavior: {mode: 'static', lookAtPlayers: false, randomStroll: false}, + attributes: {maxHealth: 10, movementSpeed: 0, followRange: 16}, + rendering: {scale: 1, shadowRadius: 0.5}, + animation: { + mode: blockEntityAnimationMode(presetType), + swingSpeed: 1, + walkSpeedMultiplier: 1 + } + }; +} - module.exports = { - SCHEMA_VERSION, - MODEL_TYPE_ENTITY, - MODEL_TYPE_BLOCK_ENTITY, - MODEL_TYPES, - GROUND_ENTITY, - STATIC_ENTITY, - PRESET_TYPES, - BLOCK_ENTITY_PRESET_TYPES, - SELECTABLE_PRESET_TYPES, - STABLE_PRESET_TYPES, - isStablePreset, - isCustom, - isStill, - isMoving, - bodyType, - movementType, - entityType, - behaviorMode, - behaviorModeFor, - movementDefaults, - animationMode, - blockEntityAnimationMode, - presetDimensions, - presetRenderBounds, - presetDefaults, - blockEntityPresetDefaults - }; +function presetDefaults(presetType, modelType) { + if (modelType === MODEL_TYPE_BLOCK_ENTITY) { + return blockEntityPresetDefaults(presetType); + } - /***/ + const move = movementType(presetType); + const ground = move === 'ground'; + const speed = ground ? 0.22 : 0; + const mode = behaviorMode(presetType); + + return { + schemaVersion: SCHEMA_VERSION, + modelType: MODEL_TYPE_ENTITY, + presetType: presetType, + host: { + entityType: entityType(presetType), + movementType: move, + bodyType: bodyType(presetType) }, + dimensions: presetDimensions(presetType), + movement: { + speed: speed, + stepHeight: ground ? 0.6 : 0, + gravity: presetType === 'static' || ground + }, + behavior: { + mode: mode, + lookAtPlayers: mode === 'idle_only' || mode === 'ambient', + randomStroll: ground && mode === 'ambient' + }, + attributes: {maxHealth: 10, movementSpeed: speed, followRange: 16}, + rendering: {scale: 1, shadowRadius: presetShadowRadius(presetType)}, + animation: { + mode: animationMode(presetType), + swingSpeed: 1, + walkSpeedMultiplier: 1 + } + }; +} + +module.exports = { + SCHEMA_VERSION, + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY, + PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + SELECTABLE_PRESET_TYPES, + isStablePreset, + isCustom, + bodyType, + behaviorModeFor, + movementDefaults, + animationMode, + presetDimensions, + presetShadowRadius, + presetDefaults +}; + + +/***/ }, + +/***/ 668 +(module, __unused_webpack_exports, __webpack_require__) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const { + PRESET_TYPES, + SELECTABLE_PRESET_TYPES, + MODEL_TYPE_ENTITY, + presetDefaults +} = __webpack_require__(151); + +const DEFAULT_PRESET = 'statue'; + +function deepMerge(target, source) { + const result = Array.isArray(target) ? target.slice() : ({ + ...target + }); + Object.keys(source).forEach((key) => { + const value = source[key]; + if (value && typeof value === 'object' && !Array.isArray(value)) { + result[key] = deepMerge(result[key] || {}, value); + } else if (Array.isArray(value)) { + result[key] = value.slice(); + } else { + result[key] = value; + } + }); - /***/ 668 - (module, __unused_webpack_exports, __webpack_require__) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - const { - SCHEMA_VERSION, - PRESET_TYPES, - BLOCK_ENTITY_PRESET_TYPES, - SELECTABLE_PRESET_TYPES, - MODEL_TYPE_ENTITY, - presetDefaults - } = __webpack_require__(151); - - const DEFAULT_PRESET = 'statue'; - - function deepMerge(target, source) { - const result = Array.isArray(target) ? target.slice() : ({ - ...target - }); - Object.keys(source).forEach((key) => { - const value = source[key]; - if (value && typeof value === 'object' && !Array.isArray(value)) { - result[key] = deepMerge(result[key] || {}, value); - } else if (Array.isArray(value)) { - result[key] = value.slice(); - } else { - result[key] = value; - } - }); - return result; - } + return result; +} - function getDefaults() { - return deepMerge(presetDefaults(DEFAULT_PRESET), { - namespace: 'example_org', - profileId: 'entity', - targetVersion: '1.20.1' - }); - } +function getDefaults() { + return deepMerge(presetDefaults(DEFAULT_PRESET), { + namespace: 'example_org', + profileId: 'entity', + targetVersion: '1.20.1' + }); +} // Full settings object for a preset type, including the identity fields. The // model type disambiguates presets that exist for both entities and block // entities (e.g. "static"). - function applyTemplate(presetType, modelType) { - return deepMerge(getDefaults(), - presetDefaults(presetType, modelType || MODEL_TYPE_ENTITY)); - } - - module.exports = { - SCHEMA_VERSION, - DEFAULT_PRESET, - PRESET_TYPES, - BLOCK_ENTITY_PRESET_TYPES, - SELECTABLE_PRESET_TYPES, - getDefaults, - applyTemplate, - presetDefaults, - deepMerge - }; - - /***/ - }, - - /***/ 954 - (module) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - const DEFAULT_VERSION_ID = '1.20.1'; - - const VERSIONS = [ - { - id: '1.20.1', - label: 'Minecraft 1.20.1', - dataFormat: 15, - resourceFormat: 15, - enabled: true - }, - { - id: '1.21.1', - label: 'Minecraft 1.21.1', - dataFormat: 48, - resourceFormat: 34, - enabled: false - }, - { - id: '1.21.11', - label: 'Minecraft 1.21.11', - dataFormat: null, - resourceFormat: null, - enabled: false - }, - { - id: '26.1.2', - label: 'Minecraft 26.1.2', - dataFormat: null, - resourceFormat: null, - enabled: false - } - ]; - - function getVersions() { - return VERSIONS.map((version) => ({...version})); - } - - function getVersion(id) { - return VERSIONS.find((version) => version.id === id) || null; - } - - function getEnabledVersions() { - return getVersions().filter((version) => version.enabled); - } - - function getDefaultVersionId() { - return DEFAULT_VERSION_ID; - } - - function getPackFormats(id) { - const version = getVersion(id); - if (!version?.enabled) { - return null; - } - return { - dataFormat: version.dataFormat, - resourceFormat: version.resourceFormat - }; - } +function applyTemplate(presetType, modelType) { + return deepMerge(getDefaults(), + presetDefaults(presetType, modelType || MODEL_TYPE_ENTITY)); +} + +module.exports = { + DEFAULT_PRESET, + PRESET_TYPES, + SELECTABLE_PRESET_TYPES, + getDefaults, + applyTemplate, + deepMerge +}; + + +/***/ }, + +/***/ 954 +(module) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const DEFAULT_VERSION_ID = '1.20.1'; + +const VERSIONS = [ + { + id: '1.20.1', + label: 'Minecraft 1.20.1', + dataFormat: 15, + resourceFormat: 15, + enabled: true + }, + { + id: '1.21.1', + label: 'Minecraft 1.21.1', + dataFormat: 48, + resourceFormat: 34, + enabled: false + }, + { + id: '1.21.11', + label: 'Minecraft 1.21.11', + dataFormat: null, + resourceFormat: null, + enabled: false + }, + { + id: '26.1.2', + label: 'Minecraft 26.1.2', + dataFormat: null, + resourceFormat: null, + enabled: false + } +]; - module.exports = { - getVersions, - getEnabledVersions, - getDefaultVersionId, - getPackFormats - }; +function getVersions() { + return VERSIONS.map((version) => ({...version})); +} - /***/ - }, +function getVersion(id) { + return VERSIONS.find((version) => version.id === id) || null; +} - /***/ 924 - (module, __unused_webpack_exports, __webpack_require__) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - const {getVersions} = __webpack_require__(954); - const {getDefaults, applyTemplate, deepMerge} = __webpack_require__(668); - const { - SELECTABLE_PRESET_TYPES, - BLOCK_ENTITY_PRESET_TYPES, - isStablePreset, - MODEL_TYPE_ENTITY, - MODEL_TYPE_BLOCK_ENTITY - } = __webpack_require__(151); - const {ModelDimensions} = __webpack_require__(763); - const {t} = __webpack_require__(16); - - function presetLabel(id) { - return id === 'custom' ? t('eme.preset.custom') : t(`eme.preset.${id}`); - } +function getEnabledVersions() { + return getVersions().filter((version) => version.enabled); +} - function entityPresetIds(experimental) { - return experimental - ? ['custom', ...SELECTABLE_PRESET_TYPES] - : SELECTABLE_PRESET_TYPES.filter(isStablePreset); - } +function getDefaultVersionId() { + return DEFAULT_VERSION_ID; +} - function blockPresetIds(experimental) { - return experimental - ? BLOCK_ENTITY_PRESET_TYPES.slice() - : BLOCK_ENTITY_PRESET_TYPES.filter(isStablePreset); - } +function getPackFormats(id) { + const version = getVersion(id); + if (!version?.enabled) { + return null; + } + return { + dataFormat: version.dataFormat, + resourceFormat: version.resourceFormat + }; +} + +module.exports = { + getVersions, + getEnabledVersions, + getDefaultVersionId, + getPackFormats +}; + + +/***/ }, + +/***/ 924 +(module, __unused_webpack_exports, __webpack_require__) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {getVersions} = __webpack_require__(954); +const {getDefaults, applyTemplate, deepMerge} = __webpack_require__(668); +const { + SELECTABLE_PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + isStablePreset, + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY +} = __webpack_require__(151); +const {ModelDimensions} = __webpack_require__(763); +const {t} = __webpack_require__(16); + +function presetLabel(id) { + return id === 'custom' ? t('eme.preset.custom') : t(`eme.preset.${id}`); +} + +function entityPresetIds(experimental) { + return experimental + ? ['custom', ...SELECTABLE_PRESET_TYPES] + : SELECTABLE_PRESET_TYPES.filter(isStablePreset); +} + +function blockPresetIds(experimental) { + return experimental + ? BLOCK_ENTITY_PRESET_TYPES.slice() + : BLOCK_ENTITY_PRESET_TYPES.filter(isStablePreset); +} // Experimental (WIP) presets are hidden unless the experimental setting is on. // `ensure` keeps an already-selected preset visible even when experimental is // off, so reopening a project that used one never breaks the dropdown. - function presetOptions(modelType, experimental, ensure) { - const ids = modelType === MODEL_TYPE_BLOCK_ENTITY - ? blockPresetIds(experimental) : entityPresetIds(experimental); - if (ensure && !ids.includes(ensure)) { - ids.push(ensure); - } - const options = {}; - ids.forEach((id) => { - options[id] = presetLabel(id); - }); - return options; - } - - function modelTypeOptions() { - return { - [MODEL_TYPE_ENTITY]: t('eme.modelType.entity'), - [MODEL_TYPE_BLOCK_ENTITY]: t('eme.modelType.block_entity') - }; - } - - function exportTypeOptions() { - return { - packs: t('eme.exportType.packs'), - mod_project: t('eme.exportType.mod_project'), - model_only: t('eme.exportType.model_only') - }; - } - - function hostEntityTypeOptions() { - return { - 'easy_model_entities:ground_entity': t('eme.entity.ground_entity'), - 'easy_model_entities:static_entity': t('eme.entity.static_entity') - }; - } - - function movementTypeOptions() { - return { - ground: t('eme.movement.ground'), - static: t('eme.movement.static') - }; - } - - function bodyTypeOptions() { - return { - static: t('eme.body.static'), - biped: t('eme.body.biped'), - quadruped: t('eme.body.quadruped'), - aquatic: t('eme.body.aquatic'), - winged: t('eme.body.winged'), - winged_humanoid: t('eme.body.winged_humanoid'), - arthropod: t('eme.body.arthropod'), - cuboid: t('eme.body.cuboid'), - floating: t('eme.body.floating') - }; - } - - function behaviorModeOptions() { - return { - idle_only: t('eme.behavior.idle_only'), - ambient: t('eme.behavior.ambient'), - static: t('eme.behavior.static'), - external_owner: t('eme.behavior.external_owner') - }; - } - - function animationModeOptions() { - return { - automatic: t('eme.animation.automatic'), - random_idle: t('eme.animation.random_idle'), - none: t('eme.animation.none') - }; - } - - function versionOptions() { - const options = {}; - getVersions().forEach((version) => { - options[version.id] = version.enabled ? version.label - : `${version.label} (coming soon)`; - }); - return options; - } +function presetOptions(modelType, experimental, ensure) { + const ids = modelType === MODEL_TYPE_BLOCK_ENTITY + ? blockPresetIds(experimental) : entityPresetIds(experimental); + if (ensure && !ids.includes(ensure)) { + ids.push(ensure); + } + const options = {}; + ids.forEach((id) => { + options[id] = presetLabel(id); + }); - function settingsToForm(settings) { - return { - namespace: settings.namespace, - profileId: settings.profileId, - version: settings.version || '', - targetVersion: settings.targetVersion, - hostEntityType: settings.host.entityType, - movementType: settings.host.movementType, - bodyType: settings.host.bodyType, - width: settings.dimensions.width, - height: settings.dimensions.height, - eyeHeight: settings.dimensions.eyeHeight, - speed: settings.movement.speed, - stepHeight: settings.movement.stepHeight, - gravity: settings.movement.gravity, - behaviorMode: settings.behavior.mode, - maxHealth: settings.attributes.maxHealth, - movementSpeed: settings.attributes.movementSpeed, - followRange: settings.attributes.followRange, - scale: settings.rendering.scale, - shadowRadius: settings.rendering.shadowRadius, - visibleBoundsWidth: settings.rendering.visibleBoundsWidth, - visibleBoundsHeight: settings.rendering.visibleBoundsHeight, - visibleBoundsOffset: settings.rendering.visibleBoundsOffset.slice(), - animationMode: settings.animation.mode, - swingSpeed: settings.animation.swingSpeed, - walkSpeedMultiplier: settings.animation.walkSpeedMultiplier - }; - } + return options; +} + +function modelTypeOptions() { + return { + [MODEL_TYPE_ENTITY]: t('eme.modelType.entity'), + [MODEL_TYPE_BLOCK_ENTITY]: t('eme.modelType.block_entity') + }; +} + +function exportTypeOptions() { + return { + packs: t('eme.exportType.packs'), + mod_project: t('eme.exportType.mod_project'), + model_only: t('eme.exportType.model_only') + }; +} + +function hostEntityTypeOptions() { + return { + 'easy_model_entities:ground_entity': t('eme.entity.ground_entity'), + 'easy_model_entities:static_entity': t('eme.entity.static_entity') + }; +} + +function movementTypeOptions() { + return {ground: t('eme.movement.ground'), static: t('eme.movement.static')}; +} + +function bodyTypeOptions() { + return { + static: t('eme.body.static'), + biped: t('eme.body.biped'), + quadruped: t('eme.body.quadruped'), + aquatic: t('eme.body.aquatic'), + winged: t('eme.body.winged'), + winged_humanoid: t('eme.body.winged_humanoid'), + arthropod: t('eme.body.arthropod'), + cuboid: t('eme.body.cuboid'), + floating: t('eme.body.floating') + }; +} + +function behaviorModeOptions() { + return { + idle_only: t('eme.behavior.idle_only'), + ambient: t('eme.behavior.ambient'), + static: t('eme.behavior.static'), + external_owner: t('eme.behavior.external_owner') + }; +} + +function animationModeOptions() { + return { + automatic: t('eme.animation.automatic'), + random_idle: t('eme.animation.random_idle'), + none: t('eme.animation.none') + }; +} + +function versionOptions() { + const options = {}; + getVersions().forEach((version) => { + options[version.id] = version.enabled ? version.label + : `${version.label} (coming soon)`; + }); - function formToSettings(form, base) { - return { - schemaVersion: base.schemaVersion, - modelType: base.modelType, - presetType: base.presetType, - version: base.version || '', - namespace: String(form.namespace || '').trim(), - profileId: String(form.profileId || '').trim(), - targetVersion: base.targetVersion, - host: { - entityType: form.hostEntityType, - movementType: form.movementType, - bodyType: form.bodyType - }, - dimensions: { - width: Number(form.width), - height: Number(form.height), - eyeHeight: Number(form.eyeHeight) - }, - movement: { - speed: Number(form.speed), - stepHeight: Number(form.stepHeight), - gravity: !!form.gravity - }, - behavior: { - mode: form.behaviorMode, - lookAtPlayers: base.behavior ? base.behavior.lookAtPlayers : true, - randomStroll: base.behavior ? base.behavior.randomStroll : false - }, - attributes: { - maxHealth: Number(form.maxHealth), - movementSpeed: Number(form.movementSpeed), - followRange: Number(form.followRange) - }, - rendering: { - scale: Number(form.scale), - shadowRadius: Number(form.shadowRadius), - visibleBoundsWidth: Number(form.visibleBoundsWidth), - visibleBoundsHeight: Number(form.visibleBoundsHeight), - visibleBoundsOffset: (form.visibleBoundsOffset || [0, 0, 0]).map( - Number) - }, - animation: { - mode: form.animationMode, - swingSpeed: Number(form.swingSpeed), - walkSpeedMultiplier: Number(form.walkSpeedMultiplier) - } - }; - } + return options; +} + +function settingsToForm(settings) { + return { + namespace: settings.namespace, + profileId: settings.profileId, + targetVersion: settings.targetVersion, + hostEntityType: settings.host.entityType, + movementType: settings.host.movementType, + bodyType: settings.host.bodyType, + width: settings.dimensions.width, + height: settings.dimensions.height, + eyeHeight: settings.dimensions.eyeHeight, + speed: settings.movement.speed, + stepHeight: settings.movement.stepHeight, + gravity: settings.movement.gravity, + behaviorMode: settings.behavior.mode, + maxHealth: settings.attributes.maxHealth, + movementSpeed: settings.attributes.movementSpeed, + followRange: settings.attributes.followRange, + scale: settings.rendering.scale, + shadowRadius: settings.rendering.shadowRadius, + animationMode: settings.animation.mode, + swingSpeed: settings.animation.swingSpeed, + walkSpeedMultiplier: settings.animation.walkSpeedMultiplier + }; +} + +function formToSettings(form, base) { + return { + schemaVersion: base.schemaVersion, + modelType: base.modelType, + presetType: base.presetType, + namespace: String(form.namespace || '').trim(), + profileId: String(form.profileId || '').trim(), + targetVersion: base.targetVersion, + host: { + entityType: form.hostEntityType, + movementType: form.movementType, + bodyType: form.bodyType + }, + dimensions: { + width: Number(form.width), + height: Number(form.height), + eyeHeight: Number(form.eyeHeight) + }, + movement: { + speed: Number(form.speed), + stepHeight: Number(form.stepHeight), + gravity: !!form.gravity + }, + behavior: { + mode: form.behaviorMode, + lookAtPlayers: base.behavior ? base.behavior.lookAtPlayers : true, + randomStroll: base.behavior ? base.behavior.randomStroll : false + }, + attributes: { + maxHealth: Number(form.maxHealth), + movementSpeed: Number(form.movementSpeed), + followRange: Number(form.followRange) + }, + rendering: { + scale: Number(form.scale), + shadowRadius: Number(form.shadowRadius) + }, + animation: { + mode: form.animationMode, + swingSpeed: Number(form.swingSpeed), + walkSpeedMultiplier: Number(form.walkSpeedMultiplier) + } + }; +} // Active preset depends on whether a block entity datapack is being authored. // Model-only export still authors a render (body) preset, so it uses the entity // preset list. - function activeModelType(form) { - return form.exportType === 'model_only' - ? MODEL_TYPE_ENTITY : (form.modelType || MODEL_TYPE_ENTITY); - } - - function activePreset(form) { - return activeModelType(form) === MODEL_TYPE_BLOCK_ENTITY - ? (form.blockPreset || 'static') : (form.preset || 'custom'); - } - - function presetFormValues(presetType, modelType, modelDimensions) { - const settings = ModelDimensions.applyModelDimensions( - applyTemplate(presetType, modelType), modelDimensions); - const form = settingsToForm(settings); - delete form.namespace; - delete form.profileId; - delete form.targetVersion; - delete form.version; - return form; - } - - function resolveExportSettings(form, base, modelDimensions) { - const exportType = form.exportType || 'packs'; - const modelOnly = exportType === 'model_only'; - const modelType = activeModelType(form); - const blockEntity = modelType === MODEL_TYPE_BLOCK_ENTITY; - const preset = activePreset(form); - - let settings; - if (!blockEntity && preset === 'custom') { - settings = deepMerge(getDefaults(), base); - } else { - settings = ModelDimensions.applyModelDimensions( - applyTemplate(preset, modelType), modelDimensions); - } - - settings.schemaVersion = base.schemaVersion || settings.schemaVersion; - settings.modelType = modelType; - settings.presetType = preset; - settings.namespace = String(form.namespace || '').trim(); - settings.profileId = String(form.profileId || '').trim(); - settings.version = String(form.version || '').trim(); - settings.targetVersion = form.targetVersion; - - if (form.customize) { - settings = formToSettings(form, settings); - } - // Remembered so a customized project reopens with its values (and target). - settings.customize = !!form.customize; - settings.exportType = exportType; - settings.exportTarget = exportType === 'packs' ? 'packs' - : 'mod_project'; - settings.modelOnly = modelOnly; - return settings; - } - - function advancedField(field, showCustomize) { - return Object.assign({condition: showCustomize}, field); - } - - function buildFormConfig(settings, ui) { - const values = settingsToForm(settings); - const state = ui || {}; - const allowCustomize = !!state.showCustomization; - - const standalone = (form) => form.exportType !== 'model_only'; - const blockEntitySelected = (form) => - standalone(form) && form.modelType === MODEL_TYPE_BLOCK_ENTITY; - const customizeOn = (form) => allowCustomize && !!form.customize; - // Server-profile advanced fields only make sense for a standalone datapack. - const showServer = (form) => customizeOn(form) && standalone(form); - // Host/movement/behavior/attributes only apply to standalone entities. - const showEntity = (form) => showServer(form) && !blockEntitySelected( - form); - // Render-profile advanced fields apply to every export kind. - const showRender = customizeOn; - - const experimental = !!state.experimental; - const entityValue = state.preset || 'statue'; - const blockValue = state.blockPreset || 'static'; - - const config = { - exportType: { - label: t('eme.field.exportType'), - type: 'select', - options: exportTypeOptions(), - value: state.exportType || 'packs' - }, - modelType: { - label: t('eme.field.modelType'), - type: 'select', - options: modelTypeOptions(), - value: state.modelType || MODEL_TYPE_ENTITY, - condition: standalone - }, - preset: { - label: t('eme.field.preset'), - type: 'select', - options: presetOptions(MODEL_TYPE_ENTITY, experimental, - entityValue), - value: entityValue, - condition: (form) => !blockEntitySelected(form) - }, - blockPreset: { - label: t('eme.field.preset'), - type: 'select', - options: presetOptions(MODEL_TYPE_BLOCK_ENTITY, experimental, - blockValue), - value: blockValue, - condition: blockEntitySelected - }, - namespace: { - label: t('eme.field.namespace'), - type: 'text', - value: values.namespace, - placeholder: 'your_mod_id' - }, - profileId: { - label: t('eme.field.profileId'), type: 'text', - value: values.profileId - }, - version: { - label: t('eme.field.version'), - type: 'text', - value: values.version, - placeholder: 'v1' - }, - targetVersion: { - label: t('eme.field.targetVersion'), - type: 'select', - options: versionOptions(), - value: values.targetVersion - } - }; - - if (allowCustomize) { - config.customize = { - label: t('eme.field.customize'), - type: 'checkbox', - value: !!state.customize - }; - } - - Object.assign(config, { - host_header: advancedField( - {type: 'info', text: `### ${t('eme.section.host')}`}, showEntity), - hostEntityType: advancedField({ - label: t('eme.field.hostEntityType'), - type: 'select', - options: hostEntityTypeOptions(), - value: values.hostEntityType - }, showEntity), - movementType: advancedField({ - label: t('eme.field.movementType'), - type: 'select', - options: movementTypeOptions(), - value: values.movementType - }, showEntity), - bodyType: advancedField({ - label: t('eme.field.bodyType'), - type: 'select', - options: bodyTypeOptions(), - value: values.bodyType - }, showEntity), - - dimensions_header: advancedField( - {type: 'info', text: `### ${t('eme.section.dimensions')}`}, - showServer), - width: advancedField( - { - label: t('eme.field.width'), - type: 'number', - value: values.width, - step: 0.1 - }, showServer), - height: advancedField( - { - label: t('eme.field.height'), - type: 'number', - value: values.height, - step: 0.1 - }, showServer), - eyeHeight: advancedField( - { - label: t('eme.field.eyeHeight'), type: 'number', - value: values.eyeHeight, step: 0.1 - }, showServer), - - movement_header: advancedField( - {type: 'info', text: `### ${t('eme.section.movement')}`}, - showEntity), - speed: advancedField( - { - label: t('eme.field.speed'), - type: 'number', - value: values.speed, - step: 0.01 - }, showEntity), - stepHeight: advancedField( - { - label: t('eme.field.stepHeight'), type: 'number', - value: values.stepHeight, step: 0.1 - }, showEntity), - gravity: advancedField( - { - label: t('eme.field.gravity'), type: 'checkbox', - value: values.gravity - }, showEntity), - - behavior_header: advancedField( - {type: 'info', text: `### ${t('eme.section.behavior')}`}, - showEntity), - behaviorMode: advancedField({ - label: t('eme.field.behaviorMode'), - type: 'select', - options: behaviorModeOptions(), - value: values.behaviorMode - }, showEntity), - - attributes_header: advancedField( - {type: 'info', text: `### ${t('eme.section.attributes')}`}, - showEntity), - maxHealth: advancedField( - { - label: t('eme.field.maxHealth'), type: 'number', - value: values.maxHealth, step: 0.5 - }, showEntity), - movementSpeed: advancedField( - { - label: t('eme.field.movementSpeed'), type: 'number', - value: values.movementSpeed, step: 0.01 - }, showEntity), - followRange: advancedField( - { - label: t('eme.field.followRange'), type: 'number', - value: values.followRange, step: 1 - }, showEntity), - - rendering_header: advancedField( - {type: 'info', text: `### ${t('eme.section.rendering')}`}, - showRender), - scale: advancedField( - { - label: t('eme.field.scale'), - type: 'number', - value: values.scale, - step: 0.1 - }, showRender), - shadowRadius: advancedField( - { - label: t('eme.field.shadowRadius'), type: 'number', - value: values.shadowRadius, step: 0.1 - }, showRender), - visibleBoundsWidth: advancedField( - { - label: t('eme.field.visibleBoundsWidth'), type: 'number', - value: values.visibleBoundsWidth, step: 0.1 - }, showRender), - visibleBoundsHeight: advancedField( - { - label: t('eme.field.visibleBoundsHeight'), type: 'number', - value: values.visibleBoundsHeight, step: 0.1 - }, showRender), - visibleBoundsOffset: advancedField( - { - label: t('eme.field.visibleBoundsOffset'), type: 'vector', - value: values.visibleBoundsOffset - }, showRender), - - animation_header: advancedField( - {type: 'info', text: `### ${t('eme.section.animation')}`}, - showRender), - animationMode: advancedField({ - label: t('eme.field.animationMode'), - type: 'select', - options: animationModeOptions(), - value: values.animationMode - }, showRender), - swingSpeed: advancedField( - { - label: t('eme.field.swingSpeed'), type: 'number', - value: values.swingSpeed, step: 0.1 - }, showRender), - walkSpeedMultiplier: advancedField( - { - label: t('eme.field.walkSpeedMultiplier'), type: 'number', - value: values.walkSpeedMultiplier, step: 0.1 - }, showRender) - }); - - return config; - } - - function openExportDialog(options) { - const settings = options.settings; - const modelDimensions = options.modelDimensions; - // Tracks the active model type + preset so advanced fields refill whenever the - // user switches between entity/block entity or picks a different preset. - let lastKey = `${activeModelType(options)}|${activePreset(options)}`; - - const dialog = new Dialog({ - id: 'eme_export_dialog', - title: t('eme.dialog.title'), - width: 560, - form: buildFormConfig(settings, { - preset: options.preset, - blockPreset: options.blockPreset, - modelType: options.modelType, - exportType: options.exportType, - customize: options.customize, - showCustomization: options.showCustomization, - experimental: options.experimental - }), - onFormChange(form) { - const modelType = activeModelType(form); - const preset = activePreset(form); - const key = `${modelType}|${preset}`; - if (key !== lastKey) { - lastKey = key; - const isCustomEntity = modelType === MODEL_TYPE_ENTITY - && preset === 'custom'; - if (!isCustomEntity) { - dialog.setFormValues( - presetFormValues(preset, modelType, modelDimensions), - false); - } - } - }, - onConfirm(form) { - const finalSettings = resolveExportSettings(form, settings, - modelDimensions); - // Records that experimental presets were available for this export. - finalSettings.experimental = !!options.experimental; - options.onExport(finalSettings, finalSettings.exportTarget); - } - }); - dialog.show(); - return dialog; - } +function activeModelType(form) { + return form.exportType === 'model_only' + ? MODEL_TYPE_ENTITY : (form.modelType || MODEL_TYPE_ENTITY); +} + +function activePreset(form) { + return activeModelType(form) === MODEL_TYPE_BLOCK_ENTITY + ? (form.blockPreset || 'static') : (form.preset || 'custom'); +} + +function presetFormValues(presetType, modelType, modelDimensions) { + const form = settingsToForm(ModelDimensions.applyModelDimensions( + applyTemplate(presetType, modelType), modelDimensions)); + delete form.namespace; + delete form.profileId; + delete form.targetVersion; + + return form; +} + +function resolveExportSettings(form, base, modelDimensions) { + const exportType = form.exportType || 'packs'; + const modelType = activeModelType(form); + const preset = activePreset(form); + + let settings; + if (modelType !== MODEL_TYPE_BLOCK_ENTITY && preset === 'custom') { + settings = deepMerge(getDefaults(), base); + } else { + settings = ModelDimensions.applyModelDimensions( + applyTemplate(preset, modelType), modelDimensions); + } - module.exports = { - presetOptions, - settingsToForm, - formToSettings, - presetFormValues, - resolveExportSettings, - openExportDialog - }; + settings.schemaVersion = base.schemaVersion || settings.schemaVersion; + settings.modelType = modelType; + settings.presetType = preset; + settings.namespace = String(form.namespace || '').trim(); + settings.profileId = String(form.profileId || '').trim(); + settings.targetVersion = form.targetVersion; - /***/ + if (form.customize) { + settings = formToSettings(form, settings); + } + // Remembered so a customized project reopens with its values (and target). + settings.customize = !!form.customize; + settings.exportType = exportType; + settings.exportTarget = exportType === 'packs' ? 'packs' : 'mod_project'; + settings.modelOnly = exportType === 'model_only'; + + return settings; +} + +function advancedField(field, showCustomize) { + return Object.assign({condition: showCustomize}, field); +} + +function buildFormConfig(settings, ui) { + const values = settingsToForm(settings); + const state = ui || {}; + const allowCustomize = !!state.showCustomization; + + const standalone = (form) => form.exportType !== 'model_only'; + const blockEntitySelected = (form) => + standalone(form) && form.modelType === MODEL_TYPE_BLOCK_ENTITY; + const customizeOn = (form) => allowCustomize && !!form.customize; + // Server-profile advanced fields only make sense for a standalone datapack. + const showServer = (form) => customizeOn(form) && standalone(form); + // Host/movement/behavior/attributes only apply to standalone entities. + const showEntity = (form) => showServer(form) && !blockEntitySelected(form); + // Render-profile advanced fields apply to every export kind. + const showRender = customizeOn; + + const experimental = !!state.experimental; + const entityValue = state.preset || 'statue'; + const blockValue = state.blockPreset || 'static'; + + const config = { + exportType: { + label: t('eme.field.exportType'), + type: 'select', + options: exportTypeOptions(), + value: state.exportType || 'packs' }, + modelType: { + label: t('eme.field.modelType'), + type: 'select', + options: modelTypeOptions(), + value: state.modelType || MODEL_TYPE_ENTITY, + condition: standalone + }, + preset: { + label: t('eme.field.preset'), + type: 'select', + options: presetOptions(MODEL_TYPE_ENTITY, experimental, entityValue), + value: entityValue, + condition: (form) => !blockEntitySelected(form) + }, + blockPreset: { + label: t('eme.field.preset'), + type: 'select', + options: presetOptions(MODEL_TYPE_BLOCK_ENTITY, experimental, blockValue), + value: blockValue, + condition: blockEntitySelected + }, + namespace: { + label: t('eme.field.namespace'), + type: 'text', + value: values.namespace, + placeholder: 'your_mod_id' + }, + profileId: { + label: t('eme.field.profileId'), type: 'text', + value: values.profileId + }, + targetVersion: { + label: t('eme.field.targetVersion'), + type: 'select', + options: versionOptions(), + value: values.targetVersion + } + }; - /***/ 20 - (module) { - - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - class ResourceLocation { - static #NAMESPACE_PATTERN = /^[a-z0-9_.-]+$/; - static #PATH_PATTERN = /^[a-z0-9_./-]+$/; - - static isValidNamespace(namespace) { - return typeof namespace === 'string' - && ResourceLocation.#NAMESPACE_PATTERN.test(namespace); - } - - static isValidPath(path) { - return typeof path === 'string' - && ResourceLocation.#PATH_PATTERN.test(path) - && !path.includes('..'); - } - - static isValidResourceLocation(value) { - if (typeof value !== 'string' || !value.includes(':')) { - return false; - } - const parts = value.split(':'); - if (parts.length !== 2) { - return false; - } - return ResourceLocation.isValidNamespace(parts[0]) - && ResourceLocation.isValidPath(parts[1]); - } + if (allowCustomize) { + config.customize = { + label: t('eme.field.customize'), + type: 'checkbox', + value: !!state.customize + }; + } - static parseResourceLocation(value) { - if (!ResourceLocation.isValidResourceLocation(value)) { - return null; - } - const parts = value.split(':'); - return {namespace: parts[0], path: parts[1]}; - } + Object.assign(config, { + host_header: advancedField( + {type: 'info', text: `### ${t('eme.section.host')}`}, showEntity), + hostEntityType: advancedField({ + label: t('eme.field.hostEntityType'), + type: 'select', + options: hostEntityTypeOptions(), + value: values.hostEntityType + }, showEntity), + movementType: advancedField({ + label: t('eme.field.movementType'), + type: 'select', + options: movementTypeOptions(), + value: values.movementType + }, showEntity), + bodyType: advancedField({ + label: t('eme.field.bodyType'), + type: 'select', + options: bodyTypeOptions(), + value: values.bodyType + }, showEntity), + + dimensions_header: advancedField( + {type: 'info', text: `### ${t('eme.section.dimensions')}`}, + showServer), + width: advancedField( + { + label: t('eme.field.width'), type: 'number', value: values.width, + step: 0.1 + }, showServer), + height: advancedField( + { + label: t('eme.field.height'), type: 'number', value: values.height, + step: 0.1 + }, showServer), + eyeHeight: advancedField( + { + label: t('eme.field.eyeHeight'), type: 'number', + value: values.eyeHeight, step: 0.1 + }, showServer), - static buildResourceLocation(namespace, path) { - return `${namespace}:${path}`; - } + movement_header: advancedField( + {type: 'info', text: `### ${t('eme.section.movement')}`}, showEntity), + speed: advancedField( + { + label: t('eme.field.speed'), type: 'number', value: values.speed, + step: 0.01 + }, showEntity), + stepHeight: advancedField( + { + label: t('eme.field.stepHeight'), type: 'number', + value: values.stepHeight, step: 0.1 + }, showEntity), + gravity: advancedField( + { + label: t('eme.field.gravity'), type: 'checkbox', + value: values.gravity + }, showEntity), + + behavior_header: advancedField( + {type: 'info', text: `### ${t('eme.section.behavior')}`}, showEntity), + behaviorMode: advancedField({ + label: t('eme.field.behaviorMode'), + type: 'select', + options: behaviorModeOptions(), + value: values.behaviorMode + }, showEntity), + + attributes_header: advancedField( + {type: 'info', text: `### ${t('eme.section.attributes')}`}, + showEntity), + maxHealth: advancedField( + { + label: t('eme.field.maxHealth'), type: 'number', + value: values.maxHealth, step: 0.5 + }, showEntity), + movementSpeed: advancedField( + { + label: t('eme.field.movementSpeed'), type: 'number', + value: values.movementSpeed, step: 0.01 + }, showEntity), + followRange: advancedField( + { + label: t('eme.field.followRange'), type: 'number', + value: values.followRange, step: 1 + }, showEntity), + + rendering_header: advancedField( + {type: 'info', text: `### ${t('eme.section.rendering')}`}, + showRender), + scale: advancedField( + { + label: t('eme.field.scale'), type: 'number', value: values.scale, + step: 0.1 + }, showRender), + shadowRadius: advancedField( + { + label: t('eme.field.shadowRadius'), type: 'number', + value: values.shadowRadius, step: 0.1 + }, showRender), + + animation_header: advancedField( + {type: 'info', text: `### ${t('eme.section.animation')}`}, + showRender), + animationMode: advancedField({ + label: t('eme.field.animationMode'), + type: 'select', + options: animationModeOptions(), + value: values.animationMode + }, showRender), + swingSpeed: advancedField( + { + label: t('eme.field.swingSpeed'), type: 'number', + value: values.swingSpeed, step: 0.1 + }, showRender), + walkSpeedMultiplier: advancedField( + { + label: t('eme.field.walkSpeedMultiplier'), type: 'number', + value: values.walkSpeedMultiplier, step: 0.1 + }, showRender) + }); - // e.g. "My Model.bbmodel" -> "my_model" - static sanitizeProfileId(name) { - const base = String(name || '') - .replace(/\.[^.]+$/, '') - .toLowerCase() - .replace(/[^a-z0-9_./-]+/g, '_') - .replace(/_{2,}/g, '_') - .replace(/^[_./-]+|[_./-]+$/g, ''); - return base.length > 0 ? base : 'entity'; + return config; +} + +function openExportDialog(options) { + const settings = options.settings; + const modelDimensions = options.modelDimensions; + // Tracks the active model type + preset so advanced fields refill whenever the + // user switches between entity/block entity or picks a different preset. + let lastKey = `${activeModelType(options)}|${activePreset(options)}`; + + const dialog = new Dialog({ + id: 'eme_export_dialog', + title: t('eme.dialog.title'), + width: 560, + form: buildFormConfig(settings, { + preset: options.preset, + blockPreset: options.blockPreset, + modelType: options.modelType, + exportType: options.exportType, + customize: options.customize, + showCustomization: options.showCustomization, + experimental: options.experimental + }), + onFormChange(form) { + const modelType = activeModelType(form); + const preset = activePreset(form); + const key = `${modelType}|${preset}`; + if (key !== lastKey) { + lastKey = key; + if (modelType !== MODEL_TYPE_ENTITY || preset !== 'custom') { + dialog.setFormValues( + presetFormValues(preset, modelType, modelDimensions), false); } } - - module.exports = {ResourceLocation}; - - /***/ }, + onConfirm(form) { + const finalSettings = resolveExportSettings(form, settings, + modelDimensions); + // Records that experimental presets were available for this export. + finalSettings.experimental = !!options.experimental; + options.onExport(finalSettings, finalSettings.exportTarget); + } + }); + dialog.show(); + + return dialog; +} + +module.exports = { + presetOptions, + settingsToForm, + formToSettings, + presetFormValues, + resolveExportSettings, + openExportDialog +}; + + +/***/ }, + +/***/ 20 +(module) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +class ResourceLocation { + static #NAMESPACE_PATTERN = /^[a-z0-9_.-]+$/; + static #PATH_PATTERN = /^[a-z0-9_./-]+$/; + + static isValidNamespace(namespace) { + return typeof namespace === 'string' + && ResourceLocation.#NAMESPACE_PATTERN.test(namespace); + } - /***/ 768 - (module) { + static isValidPath(path) { + return typeof path === 'string' && ResourceLocation.#PATH_PATTERN.test(path) + && !path.includes('..'); + } + + static isValidResourceLocation(value) { + if (typeof value !== 'string' || !value.includes(':')) { + return false; + } + const parts = value.split(':'); + if (parts.length !== 2) { + return false; + } + return ResourceLocation.isValidNamespace(parts[0]) + && ResourceLocation.isValidPath(parts[1]); + } - "use strict"; - module.exports = "\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n"; + static parseResourceLocation(value) { + if (!ResourceLocation.isValidResourceLocation(value)) { + return null; + } + const parts = value.split(':'); + return {namespace: parts[0], path: parts[1]}; + } - /***/ - }, + static buildResourceLocation(namespace, path) { + return `${namespace}:${path}`; + } - /***/ 377 - (module) { + // e.g. "My Model.bbmodel" -> "my_model" + static sanitizeProfileId(name) { + const base = String(name || '') + .replace(/\.[^.]+$/, '') + .toLowerCase() + .replace(/[^a-z0-9_./-]+/g, '_') + .replace(/_{2,}/g, '_') + .replace(/^[_./-]+|[_./-]+$/g, ''); + return base.length > 0 ? base : 'entity'; + } +} + +module.exports = {ResourceLocation}; + + +/***/ }, + +/***/ 834 +(module) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Resolves Blockbench textures to mod resource locations. A texture that lives +// under an existing assets//textures tree (vanilla or another mod) is only +// referenced; everything else is a custom texture packed as a PNG file. + +const ASSETS_PATTERN = /assets\/([a-z0-9_.-]+)\/textures\/(.+?)(?:\.png)?$/i; + +function stripPng(value) { + return value.replace(/\.png$/i, ''); +} + +function trimSlashes(value) { + return value.replace(/^\/+|\/+$/g, ''); +} + +function defaultTextureLocation(settings) { + return `${settings.namespace}:textures/entity/${settings.profileId}.png`; +} + +function customFileName(settings, index) { + return index === 0 + ? `${settings.profileId}.png` + : `${settings.profileId}_${index}.png`; +} + +function customLocation(settings, index) { + return `${settings.namespace}:textures/entity/${customFileName(settings, + index)}`; +} + +function parseExternalLocation(descriptor) { + const path = String(descriptor.path || '').replace(/\\/g, '/'); + const match = path.match(ASSETS_PATTERN); + if (match) { + return `${match[1].toLowerCase()}:textures/${match[2].toLowerCase()}.png`; + } - "use strict"; - module.exports = "# {{id}} - Easy Model Entities pack\n\nGenerated by the **Easy Model Entities Exporter** Blockbench plugin for the **Easy Model Entities**\nmod on **{{mcVersion}}**.\n\n> **Beta:** The exporter is still in beta. The pack format may change in a future version, so you\n> might need to re-export this pack after updating the plugin or the mod.\n\nThis archive contains two ready-to-use packs that belong together:\n\n- `datapack.zip`: the server-side entity profile (gameplay data)\n- `resourcepack.zip`: the model, texture and render profile (visuals)\n\nProfile ID: `{{serverProfileId}}`\n\n## Requirements\n\nYou need the **Easy Model Entities** mod installed for **{{mcVersion}}** (Forge or Fabric).\nWithout the mod these files do nothing. Get the mod from its distribution page (CurseForge /\nModrinth) and install it like any other mod before using this pack.\n\n## Installation\n\nYou do not need to unpack anything — just move the two ZIP files into the right folders.\n\n### 1. Resource Pack (visuals, client side)\n\nMove `resourcepack.zip` into your Minecraft resourcepacks folder:\n\n- Windows: `%appdata%\\.minecraft\\resourcepacks\\`\n- Linux: `~/.minecraft/resourcepacks/`\n- macOS: `~/Library/Application Support/minecraft/resourcepacks/`\n\nThen start Minecraft and enable the pack under **Options > Resource Packs**.\n\n### 2. Data Pack (gameplay, world / server side)\n\nMove `datapack.zip` into your world's `datapacks` folder:\n\n- Single player: `.minecraft/saves//datapacks/`\n- Dedicated server: `/world/datapacks/`\n\nLoad the world (or run `/reload` in game). On a dedicated server the Easy Model Entities mod must be\ninstalled on the server as well.\n\n> Install **both** packs. The data pack defines the entity, the resource pack provides its model and\n> texture.\n"; + const namespace = String(descriptor.namespace || '').trim().toLowerCase(); + if (!namespace) { + return null; + } - /***/ - }, + const folder = trimSlashes(String(descriptor.folder || '').trim()); + const name = stripPng(String(descriptor.name || '').trim()).toLowerCase(); + if (!name) { + return null; + } - /***/ 896 - (module) { + const rest = folder ? `${folder.toLowerCase()}/${name}` : name; + + return `${namespace}:textures/${rest}.png`; +} + +function resolveTextures(descriptors, settings) { + const defaultLocation = defaultTextureLocation(settings); + const textures = {}; + const packed = []; + let texture = null; + + (descriptors || []).forEach((descriptor, position) => { + const index = Number.isInteger(descriptor.index) ? descriptor.index + : position; + let location = parseExternalLocation(descriptor); + if (!location) { + location = customLocation(settings, index); + packed.push({ + fileName: customFileName(settings, index), + bytes: descriptor.bytes + }); + } - "use strict"; - module.exports = require("fs"); + // Index 0 follows the mod's default path unless it deviates (e.g. vanilla); + // every other index is always spelled out in the textures map. + if (index === 0) { + if (location !== defaultLocation) { + texture = location; + } + } else { + textures[index] = location; + } + }); - /***/ - }, + return {texture, textures, packed}; +} + +module.exports = {resolveTextures, parseExternalLocation}; + + +/***/ }, + +/***/ 803 +(module) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Deterministic, dependency-free 32-bit FNV-1a hash rendered as 8 hex chars. +// Used to derive a stable content version stamp without requiring node crypto. +function hashString(value) { + let hash = 0x811c9dc5; + const text = String(value); + for (let i = 0; i < text.length; i++) { + hash ^= text.codePointAt(i); + hash = Math.imul(hash, 0x01000193); + } - /***/ 928 - (module) { + return (hash >>> 0).toString(16).padStart(8, '0'); +} + +module.exports = {hashString}; + + +/***/ }, + +/***/ 734 +(module) { + +"use strict"; +module.exports = ""; + +/***/ }, + +/***/ 448 +(module) { + +"use strict"; +module.exports = ""; + +/***/ }, + +/***/ 377 +(module) { + +"use strict"; +module.exports = "# {{id}} - Easy Model Entities pack\n\nGenerated by the **Easy Model Entities Exporter** Blockbench plugin for the **Easy Model Entities**\nmod on **{{mcVersion}}**.\n\n> **Beta:** The exporter is still in beta. The pack format may change in a future version, so you\n> might need to re-export this pack after updating the plugin or the mod.\n\nThis archive contains two ready-to-use packs that belong together. Their file names are prefixed\nwith the model id so several exported packs can live in the same folder without overwriting each\nother:\n\n- `{{datapackFile}}`: the server-side entity profile (gameplay data)\n- `{{resourcepackFile}}`: the model, texture and render profile (visuals)\n\nProfile ID: `{{serverProfileId}}`\n\n## Model Format\n\nThis pack was exported using the **Easy Model Entity** format (`eme_entity`), which is provided by\nthis plugin and tailored specifically for the Easy Model Entities mod. If you need to re-edit the\nmodel, open it in Blockbench and select **Easy Model Entity** as the format. Alternatively, the\nstandard **Modded Entity** format (built into Blockbench) is also supported and can be exported\nwith the same plugin.\n\n## Requirements\n\nYou need the **Easy Model Entities** mod installed for **{{mcVersion}}** (Forge or Fabric).\nWithout the mod these files do nothing. Get the mod from:\n\n- [CurseForge](https://www.curseforge.com/minecraft/mc-mods/easy-model-entities)\n- [Modrinth](https://modrinth.com/mod/easy-model-entities)\n\nInstall it like any other mod before using this pack.\n\n## Installation\n\nYou do not need to unpack anything — just move the two ZIP files into the right folders.\n\n### 1. Resource Pack (visuals, client side)\n\nMove `{{resourcepackFile}}` into your Minecraft resourcepacks folder:\n\n- Windows: `%appdata%\\.minecraft\\resourcepacks\\`\n- Linux: `~/.minecraft/resourcepacks/`\n- macOS: `~/Library/Application Support/minecraft/resourcepacks/`\n\nThen start Minecraft and enable the pack under **Options > Resource Packs**.\n\n### 2. Data Pack (gameplay, world / server side)\n\nMove `{{datapackFile}}` into your world's `datapacks` folder:\n\n- Single player: `.minecraft/saves//datapacks/`\n- Dedicated server: `/world/datapacks/`\n\nLoad the world (or run `/reload` in game). On a dedicated server the Easy Model Entities mod must be\ninstalled on the server as well.\n\n> Install **both** packs. The data pack defines the entity, the resource pack provides its model and\n> texture.\n"; + +/***/ }, + +/***/ 896 +(module) { + +"use strict"; +module.exports = require("fs"); + +/***/ }, + +/***/ 928 +(module) { + +"use strict"; +module.exports = require("path"); + +/***/ } + +/******/ }); +/************************************************************************/ +/******/ // The module cache +/******/ var __webpack_module_cache__ = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ // Check if module is in cache +/******/ var cachedModule = __webpack_module_cache__[moduleId]; +/******/ if (cachedModule !== undefined) { +/******/ return cachedModule.exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = __webpack_module_cache__[moduleId] = { +/******/ // no module.id needed +/******/ // no module.loaded needed +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/************************************************************************/ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const { + getDefaults, + deepMerge, + applyTemplate, + DEFAULT_PRESET +} = __webpack_require__(668); +const {ModelDimensions} = __webpack_require__(763); +const {PresetDetector} = __webpack_require__(858); +const {ResourceLocation} = __webpack_require__(20); +const {resolveTextures} = __webpack_require__(834); +const {Validator} = __webpack_require__(229); +const {buildPackBundle, buildModProjectFiles} = __webpack_require__(869); +const {BlockbenchAdapter} = __webpack_require__(833); +const {openExportDialog} = __webpack_require__(924); +const {registerTranslations, t} = __webpack_require__(16); +const exportIconSvg = __webpack_require__(448); +const createIconSvg = __webpack_require__(734); +const {FORMAT_ID, registerEmeFormat, unregisterEmeFormat} = __webpack_require__( + 995); +const {registerEmeCodecHooks, unregisterEmeCodecHooks} = __webpack_require__( + 670); + +const MODDED_ENTITY_FORMAT_ID = 'modded_entity'; +const ACTION_ID = 'eme_export'; +const SETTING_ID = 'eme_enable_customization'; +const EXPERIMENTAL_SETTING_ID = 'eme_enable_experimental'; +const EXPORT_ICON = 'data:image/svg+xml,' + encodeURIComponent(exportIconSvg); +const CREATE_ICON = 'data:image/svg+xml,' + encodeURIComponent(createIconSvg); +let exportAction; +let projectProperty; +let customizationSetting; +let experimentalSetting; + +function customizationEnabled() { + if (customizationSetting && typeof customizationSetting.value === 'boolean') { + return customizationSetting.value; + } - "use strict"; - module.exports = require("path"); + return false; +} - /***/ - } +function experimentalEnabled() { + if (experimentalSetting && typeof experimentalSetting.value === 'boolean') { + return experimentalSetting.value; + } - /******/ - }); - /************************************************************************/ - /******/ // The module cache - /******/ - var __webpack_module_cache__ = {}; - /******/ - /******/ // The require function - /******/ - function __webpack_require__(moduleId) { - /******/ // Check if module is in cache - /******/ - var cachedModule = __webpack_module_cache__[moduleId]; - /******/ - if (cachedModule !== undefined) { - /******/ - return cachedModule.exports; - /******/ - } - /******/ // Create a new module (and put it into the cache) - /******/ - var module = __webpack_module_cache__[moduleId] = { - /******/ // no module.id needed - /******/ // no module.loaded needed - /******/ exports: {} - /******/ - }; - /******/ - /******/ // Execute the module function - /******/ - __webpack_modules__[moduleId](module, module.exports, __webpack_require__); - /******/ - /******/ // Return the exports of the module - /******/ - return module.exports; - /******/ - } - - /******/ - /************************************************************************/ - /* - * Copyright 2026 Markus Bordihn - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and - * associated documentation files (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, publish, distribute, - * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or - * substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT - * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - - const { - getDefaults, - deepMerge, - applyTemplate, - DEFAULT_PRESET - } = __webpack_require__(668); - const {ModelDimensions} = __webpack_require__(763); - const {PresetDetector} = __webpack_require__(858); - const {ResourceLocation} = __webpack_require__(20); - const {Validator} = __webpack_require__(229); - const {buildPackBundle, buildModProjectFiles} = __webpack_require__(869); - const {BlockbenchAdapter} = __webpack_require__(833); - const {openExportDialog} = __webpack_require__(924); - const {registerTranslations, t} = __webpack_require__(16); - const actionIconSvg = __webpack_require__(768); - - const ACTION_ID = 'eme_export'; - const SETTING_ID = 'eme_enable_customization'; - const EXPERIMENTAL_SETTING_ID = 'eme_enable_experimental'; - const ACTION_ICON = 'data:image/svg+xml,' + encodeURIComponent(actionIconSvg); - let exportAction; - let projectProperty; - let customizationSetting; - let experimentalSetting; - - function customizationEnabled() { - if (customizationSetting && typeof customizationSetting.value - === 'boolean') { - return customizationSetting.value; - } - return false; - } - - function experimentalEnabled() { - if (experimentalSetting && typeof experimentalSetting.value === 'boolean') { - return experimentalSetting.value; - } - return false; - } - - function resolveDialogState() { - const storedSettings = BlockbenchAdapter.loadSettings(); - const stats = BlockbenchAdapter.getModelStats(); - const bounds = BlockbenchAdapter.getModelBounds(); - - let settings; - let preset; - let blockPreset; - let modelType; - let customize; - let exportType; - - if (storedSettings) { - settings = deepMerge(getDefaults(), storedSettings); - modelType = settings.modelType || 'entity'; - customize = !!storedSettings.customize; - exportType = storedSettings.exportType - || (storedSettings.exportTarget === 'mod_project' ? 'mod_project' - : 'packs'); - if (modelType === 'block_entity') { - preset = 'custom'; - blockPreset = settings.presetType || 'static'; - } else { - preset = settings.presetType || DEFAULT_PRESET; - blockPreset = 'static'; - } + return false; +} + +function resolveDialogState() { + const storedSettings = BlockbenchAdapter.loadSettings(); + const stats = BlockbenchAdapter.getModelStats(); + const bounds = BlockbenchAdapter.getModelBounds(); + + let settings; + let preset; + let blockPreset; + let modelType; + let customize; + let exportType; + + if (storedSettings) { + settings = deepMerge(getDefaults(), storedSettings); + modelType = settings.modelType || 'entity'; + customize = !!storedSettings.customize; + exportType = storedSettings.exportType + || (storedSettings.exportTarget === 'mod_project' ? 'mod_project' + : 'packs'); + if (modelType === 'block_entity') { + preset = 'custom'; + blockPreset = settings.presetType || 'static'; } else { - preset = PresetDetector.detectPresetType(stats, bounds); + preset = settings.presetType || DEFAULT_PRESET; blockPreset = 'static'; - modelType = 'entity'; - settings = applyTemplate(preset); - ModelDimensions.applyModelDimensions(settings, - ModelDimensions.deriveDimensions(bounds, settings.host.bodyType)); - customize = false; - exportType = 'packs'; - const projectName = BlockbenchAdapter.getProjectName(); - if (projectName) { - settings.profileId = ResourceLocation.sanitizeProfileId(projectName); - } } - - const modelDimensions = ModelDimensions.deriveDimensions(bounds, - settings.host.bodyType); - return { - settings, preset, blockPreset, modelType, customize, exportType, - modelDimensions - }; + } else { + preset = PresetDetector.detectPresetType(stats, bounds); + blockPreset = 'static'; + modelType = 'entity'; + settings = applyTemplate(preset); + ModelDimensions.applyModelDimensions(settings, + ModelDimensions.deriveDimensions(bounds, settings.host.bodyType)); + customize = false; + exportType = 'packs'; } - function formatIssues(issues) { - return issues.map((issue) => `• ${issue.message}`).join('\n'); + // Derive the profile id from the model name/file whenever none has been set + // yet (empty or still the placeholder default). + const projectName = BlockbenchAdapter.getProjectName(); + if (projectName + && (!settings.profileId || settings.profileId + === getDefaults().profileId)) { + settings.profileId = ResourceLocation.sanitizeProfileId(projectName); } - function collectContext() { - const stats = BlockbenchAdapter.getModelStats(); - return Object.assign({}, stats, { - textureCount: BlockbenchAdapter.getTextureCount(), - visibleBoundsManual: true + return { + settings, preset, blockPreset, modelType, customize, exportType, + modelDimensions: ModelDimensions.deriveDimensions(bounds, + settings.host.bodyType) + }; +} + +function formatIssues(issues) { + return issues.map((issue) => `• ${issue.message}`).join('\n'); +} + +function collectContext() { + return Object.assign({}, BlockbenchAdapter.getModelStats()); +} + +function runExport(settings, target) { + const textureResolution = resolveTextures( + BlockbenchAdapter.collectTextures(), settings); + const result = Validator.validateSettings(settings, collectContext()); + + if (!result.valid) { + Blockbench.showMessageBox({ + title: t('eme.dialog.title'), + message: 'Export blocked. Please fix the following issues:\n\n' + + formatIssues(result.errors) }); + return; } - function runExport(settings, target) { - const context = collectContext(); - const result = Validator.validateSettings(settings, context); - - if (!result.valid) { - Blockbench.showMessageBox({ - title: t('eme.dialog.title'), - message: 'Export blocked. Please fix the following issues:\n\n' - + formatIssues(result.errors) - }); - return; - } - - const doExport = () => performExport(settings, target); + const doExport = () => performExport(settings, target, textureResolution); - if (result.warnings.length > 0) { - Blockbench.showMessageBox( - { - title: t('eme.dialog.title'), - message: 'The following warnings were found:\n\n' + formatIssues( - result.warnings) + '\n\nExport anyway?', - buttons: ['Export', 'Cancel'], - confirm: 0, - cancel: 1 - }, - (button) => { - if (button === 0) { - doExport(); - } + if (result.warnings.length > 0) { + Blockbench.showMessageBox( + { + title: t('eme.dialog.title'), + message: 'The following warnings were found:\n\n' + formatIssues( + result.warnings) + '\n\nExport anyway?', + buttons: ['Export', 'Cancel'], + confirm: 0, + cancel: 1 + }, + (button) => { + if (button === 0) { + doExport(); } - ); - } else { - doExport(); - } + } + ); + } else { + doExport(); } +} - function performExport(settings, target) { - const options = { - modelBytes: BlockbenchAdapter.getModelBytes(), - textureBytes: BlockbenchAdapter.getTextureBytes() - }; +function performExport(settings, target, textureResolution) { + const options = { + modelBytes: BlockbenchAdapter.getModelBytes(), + textureResolution + }; - BlockbenchAdapter.saveSettings(settings); + BlockbenchAdapter.saveSettings(settings); - if (target === 'mod_project') { - exportToModProject(settings, options); - } else { - exportToZip(settings, options); - } + if (target === 'mod_project') { + exportToModProject(settings, options); + } else { + exportToZip(settings, options); } +} + +function exportToZip(settings, options) { + BlockbenchAdapter.exportPackBundle( + buildPackBundle(settings, options), + `${settings.namespace}_${settings.profileId}_eme`) + .then(() => { + Blockbench.showQuickMessage('Easy Model Entities packs exported', 1500); + }) + .catch((error) => { + Blockbench.showMessageBox({title: 'Export failed', message: String(error)}); + }); +} - function exportToZip(settings, options) { - const bundle = buildPackBundle(settings, options); - BlockbenchAdapter.exportPackBundle(bundle, - `${settings.namespace}_${settings.profileId}_eme`) - .then(() => { - Blockbench.showQuickMessage('Easy Model Entities packs exported', 1500); - }) - .catch((error) => { - Blockbench.showMessageBox( - {title: 'Export failed', message: String(error)}); - }); +function exportToModProject(settings, options) { + const rootDir = BlockbenchAdapter.pickDirectory( + 'Select src/main/resources directory'); + if (!rootDir) { + return; } - function exportToModProject(settings, options) { - const rootDir = BlockbenchAdapter.pickDirectory( - 'Select src/main/resources directory'); - if (!rootDir) { - return; - } - - const {files} = buildModProjectFiles(settings, options); - const existing = BlockbenchAdapter.listExistingFiles(rootDir, files); + const {files} = buildModProjectFiles(settings, options); + const existing = BlockbenchAdapter.listExistingFiles(rootDir, files); - const write = () => { - try { - BlockbenchAdapter.writeToDirectory(rootDir, files); - Blockbench.showQuickMessage( - 'Easy Model Entities files written to mod project', 1500); - } catch (error) { - Blockbench.showMessageBox( - {title: 'Export failed', message: String(error)}); - } - }; - - if (existing.length > 0) { + const write = () => { + try { + BlockbenchAdapter.writeToDirectory(rootDir, files); + Blockbench.showQuickMessage( + 'Easy Model Entities files written to mod project', 1500); + } catch (error) { Blockbench.showMessageBox( - { - title: 'Overwrite existing files?', - message: `${existing.length} file(s) already exist and will be overwritten:\n\n` - + existing.join('\n'), - buttons: ['Overwrite', 'Cancel'], - confirm: 0, - cancel: 1 - }, - (button) => { - if (button === 0) { - write(); - } + {title: 'Export failed', message: String(error)}); + } + }; + + if (existing.length > 0) { + Blockbench.showMessageBox( + { + title: 'Overwrite existing files?', + message: `${existing.length} file(s) already exist and will be overwritten:\n\n` + + existing.join('\n'), + buttons: ['Overwrite', 'Cancel'], + confirm: 0, + cancel: 1 + }, + (button) => { + if (button === 0) { + write(); } - ); - } else { - write(); - } - } - - function openDialog() { - if (!Project) { - Blockbench.showQuickMessage('Open a project first', 1500); - return; - } - const state = resolveDialogState(); - openExportDialog({ - settings: state.settings, - preset: state.preset, - blockPreset: state.blockPreset, - modelType: state.modelType, - customize: state.customize, - exportType: state.exportType, - showCustomization: customizationEnabled(), - experimental: experimentalEnabled(), - modelDimensions: state.modelDimensions, - onExport: runExport - }); + } + ); + } else { + write(); } +} - BBPlugin.register('easy_model_entities', { - title: 'Easy Model Entities Exporter', - icon: 'icon.png', - author: 'Markus Bordihn', - description: 'Export Blockbench models for the Easy Model Entities mod (Minecraft: Java Edition) as ready-to-install Data Pack and Resource Pack files.', - tags: ['Minecraft: Java Edition', 'Exporter', 'Entity'], - version: '0.1.0', - min_version: '4.9.0', - variant: 'desktop', - has_changelog: true, - website: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities', - repository: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities', - bug_tracker: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities/issues', - onload() { - registerTranslations(); - - projectProperty = new Property(ModelProject, 'object', - BlockbenchAdapter.PROJECT_PROPERTY, { - label: 'Easy Model Entities Settings', - exposed: false, - default: undefined - }); - - customizationSetting = new Setting(SETTING_ID, { - name: t('eme.setting.enable_customization'), - category: 'export', - type: 'toggle', - value: false - }); +function openDialog() { + if (!Project) { + Blockbench.showQuickMessage('Open a project first', 1500); + return; + } - experimentalSetting = new Setting(EXPERIMENTAL_SETTING_ID, { - name: t('eme.setting.enable_experimental'), - category: 'export', - type: 'toggle', - value: false - }); + const state = resolveDialogState(); + openExportDialog({ + settings: state.settings, + preset: state.preset, + blockPreset: state.blockPreset, + modelType: state.modelType, + customize: state.customize, + exportType: state.exportType, + showCustomization: customizationEnabled(), + experimental: experimentalEnabled(), + modelDimensions: state.modelDimensions, + onExport: runExport + }); +} + +BBPlugin.register('easy_model_entities', { + title: 'Easy Model Entities Exporter', + icon: 'icon.png', + author: 'Markus Bordihn', + description: 'Export Blockbench models for the Easy Model Entities mod (Minecraft: Java Edition) as ready-to-install Data Pack and Resource Pack files.', + tags: ['Minecraft: Java Edition', 'Format', 'Exporter', 'Entity'], + version: '0.1.0', + min_version: '4.9.0', + variant: 'desktop', + await_loading: true, + has_changelog: true, + contributes: { + formats: ['eme_entity'] + }, + website: 'https://www.curseforge.com/minecraft/mc-mods/easy-model-entities', + repository: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities', + bug_tracker: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities/issues', + onload() { + registerTranslations(); + registerEmeFormat(CREATE_ICON); + registerEmeCodecHooks(); + + projectProperty = new Property(ModelProject, 'object', + BlockbenchAdapter.PROJECT_PROPERTY, { + label: 'Easy Model Entities Settings', + exposed: false, + default: undefined + }); - exportAction = new Action(ACTION_ID, { - name: 'Export Easy Model Entities Pack', - description: 'Export the current project as Easy Model Entities Data Pack and Resource Pack.', - icon: ACTION_ICON, - click() { - openDialog(); - } - }); + customizationSetting = new Setting(SETTING_ID, { + name: t('eme.setting.enable_customization'), + category: 'export', + type: 'toggle', + value: false + }); - MenuBar.addAction(exportAction, 'file.export'); - }, - onunload() { - if (exportAction) { - exportAction.delete(); - } - if (customizationSetting) { - customizationSetting.delete(); - } - if (experimentalSetting) { - experimentalSetting.delete(); - } - if (projectProperty) { - projectProperty.delete(); + experimentalSetting = new Setting(EXPERIMENTAL_SETTING_ID, { + name: t('eme.setting.enable_experimental'), + category: 'export', + type: 'toggle', + value: false + }); + + exportAction = new Action(ACTION_ID, { + name: 'Export Easy Model Entity Pack', + description: 'Export the current project as Easy Model Entities Data Pack and Resource Pack.', + icon: EXPORT_ICON, + condition: () => typeof Format !== 'undefined' && Format + && (Format.id === FORMAT_ID || Format.id === MODDED_ENTITY_FORMAT_ID), + click() { + openDialog(); } + }); + + MenuBar.addAction(exportAction, 'file.export'); + }, + onunload() { + unregisterEmeCodecHooks(); + unregisterEmeFormat(); + if (exportAction) { + exportAction.delete(); } - }); + if (customizationSetting) { + customizationSetting.delete(); + } + if (experimentalSetting) { + experimentalSetting.delete(); + } + if (projectProperty) { + projectProperty.delete(); + } + } +}); - /******/ -})() -; +/******/ })() +; \ No newline at end of file diff --git a/plugins/easy_model_entities/src/BlockbenchAdapter.js b/plugins/easy_model_entities/src/BlockbenchAdapter.js index 1c4966e2..946fed47 100644 --- a/plugins/easy_model_entities/src/BlockbenchAdapter.js +++ b/plugins/easy_model_entities/src/BlockbenchAdapter.js @@ -29,30 +29,46 @@ class BlockbenchAdapter { for (let i = 0; i < binary.length; i++) { bytes[i] = binary.codePointAt(i) & 0xff; } + return bytes; } - // The texture currently selected/active in Blockbench; only this texture is - // exported when the project contains multiple textures. - static getActiveTexture() { + static #firstTexture() { if (typeof Texture === 'undefined') { return null; } - return Texture.selected || Texture.getDefault() || (Texture.all?.[0]) - || null; + + return Texture.getDefault?.() || Texture.all?.[0] || null; + } + + static #textureIndex(texture, position) { + const id = parseInt(texture?.id, 10); + + return Number.isInteger(id) && id >= 0 ? id : position; } - static getTextureCount() { + // All project textures as plain descriptors for TextureResolver. External + // (vanilla / other-mod) textures are referenced by location; custom textures + // carry their PNG bytes to be packed. + static collectTextures() { if (typeof Texture === 'undefined' || !Texture.all) { - return 0; + return []; } - return Texture.all.length; + + return Texture.all.map((texture, position) => ({ + index: BlockbenchAdapter.#textureIndex(texture, position), + name: texture.name || '', + namespace: texture.namespace || '', + folder: texture.folder || '', + path: texture.path || '', + bytes: BlockbenchAdapter.#base64ToBytes(texture.getBase64()) + })); } static getModelStats() { const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; const groups = typeof Group !== 'undefined' && Group.all ? Group.all : []; - const texture = BlockbenchAdapter.getActiveTexture(); + const texture = BlockbenchAdapter.#firstTexture(); let maxDepth = 0; groups.forEach((group) => { @@ -88,6 +104,7 @@ class BlockbenchAdapter { if (cubes.length === 0) { return null; } + const bounds = { minX: Infinity, minY: Infinity, @@ -106,6 +123,7 @@ class BlockbenchAdapter { bounds.maxY = Math.max(bounds.maxY, from[1], to[1]); bounds.maxZ = Math.max(bounds.maxZ, from[2], to[2]); }); + return bounds; } @@ -113,34 +131,31 @@ class BlockbenchAdapter { if (typeof Project === 'undefined' || !Project) { return ''; } + if (Project.geometry_name) { return Project.geometry_name; } + if (Project.name) { return Project.name; } + if (typeof Project.getDisplayName === 'function') { return Project.getDisplayName(false); } return ''; } - static getTextureBytes() { - const texture = BlockbenchAdapter.getActiveTexture(); - if (!texture) { - return new Uint8Array(0); - } - return BlockbenchAdapter.#base64ToBytes(texture.getBase64()); - } - static loadSettings() { if (typeof Project === 'undefined' || !Project) { return null; } + const stored = Project[BlockbenchAdapter.PROJECT_PROPERTY]; if (stored && typeof stored === 'object') { return structuredClone(stored); } + return null; } @@ -159,6 +174,7 @@ class BlockbenchAdapter { files.forEach((file) => { zip.file(file.path, file.content); }); + return zip.generateAsync({type: 'uint8array'}); } @@ -172,8 +188,9 @@ class BlockbenchAdapter { ]).then(([datapackZip, resourcepackZip]) => { const outer = new JSZip(); outer.file(bundle.readme.path, bundle.readme.content); - outer.file('datapack.zip', datapackZip); - outer.file('resourcepack.zip', resourcepackZip); + outer.file(bundle.datapackFileName || 'datapack.zip', datapackZip); + outer.file(bundle.resourcepackFileName || 'resourcepack.zip', + resourcepackZip); return outer.generateAsync({type: 'blob'}); }).then((content) => { return new Promise((resolve) => { @@ -194,6 +211,7 @@ class BlockbenchAdapter { static listExistingFiles(rootDir, files) { const fs = require('fs'); const path = require('path'); + return files .map((file) => path.join(rootDir, file.path)) .filter((fullPath) => fs.existsSync(fullPath)); diff --git a/plugins/easy_model_entities/src/builders/exporter.js b/plugins/easy_model_entities/src/builders/exporter.js index ccc70d8c..78dec82a 100644 --- a/plugins/easy_model_entities/src/builders/exporter.js +++ b/plugins/easy_model_entities/src/builders/exporter.js @@ -21,33 +21,64 @@ const {buildServerProfile} = require('./serverProfile'); const {buildRenderProfile} = require('./renderProfile'); const {buildDataPackMcmeta, buildResourcePackMcmeta} = require('./packMeta'); const {buildReadme} = require('./readme'); +const {hashString} = require('../utils/hash'); function toJson(value) { return JSON.stringify(value, null, 2) + '\n'; } -function buildProfiles(settings) { - return { - serverProfile: buildServerProfile(settings), - renderProfile: buildRenderProfile(settings) - }; +// The version stamp is a content hash of the server profile, i.e. of exactly +// the server-relevant settings (type, preset, dimensions, behavior, …) and not +// of the model geometry or textures. Both profiles receive the same stamp so +// the mod's server/client parity check always matches; it only changes when a +// server-relevant setting changes. Model-only exports have no server profile +// and stay version-less (the mod's own runtime contract owns the version). +function stampVersion(serverProfile, renderProfile) { + if (!serverProfile) { + return; + } + + const version = hashString(toJson(serverProfile)); + serverProfile.version = version; + renderProfile.version = version; +} + +function buildProfiles(settings, textureResolution) { + const serverProfile = buildServerProfile(settings); + const renderProfile = buildRenderProfile(settings, textureResolution); + stampVersion(serverProfile, renderProfile); + + return {serverProfile, renderProfile}; } function dataPaths(settings) { - const ns = settings.namespace; + const namespace = settings.namespace; const id = settings.profileId; - // Server profiles live in an entity/ or block_entity/ subfolder. Models and - // textures stay at the mod's auto-resolved default location so the render - // profile never has to spell out a model/texture path. + // Server profiles live in an entity/ or block_entity/ subfolder. The model + // stays at the mod's auto-resolved default location so the render profile + // never has to spell out a model path. const modelType = settings.modelType || 'entity'; + return { - profile: `data/${ns}/easy_model_entities/profiles/${modelType}/${id}.json`, - renderProfile: `assets/${ns}/easy_model_entities/render_profiles/${id}.json`, - model: `assets/${ns}/easy_model_entities/models/${id}.bbmodel`, - texture: `assets/${ns}/textures/entity/${id}.png` + profile: `data/${namespace}/easy_model_entities/profiles/${modelType}/${id}.json`, + renderProfile: `assets/${namespace}/easy_model_entities/render_profiles/${id}.json`, + model: `assets/${namespace}/easy_model_entities/models/${id}.bbmodel` }; } +// One binary entry per custom (packed) texture; external textures are +// referenced by resource location and never written. +function textureFiles(settings, textureResolution) { + const namespace = settings.namespace; + const packed = (textureResolution && textureResolution.packed) || []; + + return packed.map((entry) => ({ + path: `assets/${namespace}/textures/entity/${entry.fileName}`, + content: entry.bytes, + binary: true + })); +} + function datapackFiles(settings, serverProfile) { return [ { @@ -70,19 +101,34 @@ function resourcepackFiles(settings, renderProfile, options) { }, {path: paths.renderProfile, content: toJson(renderProfile), binary: false}, {path: paths.model, content: options.modelBytes, binary: true}, - {path: paths.texture, content: options.textureBytes, binary: true} + ...textureFiles(settings, options.textureResolution) ]; } -// Bundle to wrap into the outer export ZIP: a ready-to-drop datapack.zip and -// resourcepack.zip plus an install README. +// Inner pack file names are prefixed with the profile id so a user dropping +// several exported packs into one folder never overwrites another model's pack. +function packFileNames(settings) { + return { + datapack: `${settings.profileId}_datapack.zip`, + resourcepack: `${settings.profileId}_resourcepack.zip` + }; +} + function buildPackBundle(settings, options) { const opts = options || {}; - const {serverProfile, renderProfile} = buildProfiles(settings); + const {serverProfile, renderProfile} = buildProfiles(settings, + opts.textureResolution); + const fileNames = packFileNames(settings); + return { - readme: {path: 'README.md', content: buildReadme(settings), binary: false}, + readme: { + path: 'README.md', content: buildReadme(settings, fileNames), + binary: false + }, datapack: datapackFiles(settings, serverProfile), resourcepack: resourcepackFiles(settings, renderProfile, opts), + datapackFileName: fileNames.datapack, + resourcepackFileName: fileNames.resourcepack, serverProfile, renderProfile }; @@ -90,15 +136,16 @@ function buildPackBundle(settings, options) { function buildModProjectFiles(settings, options) { const opts = options || {}; - const renderProfile = buildRenderProfile(settings); + const renderProfile = buildRenderProfile(settings, opts.textureResolution); const paths = dataPaths(settings); const files = []; - let serverProfile = null; // Model-only export (mod integration, e.g. the Mimic example): the mod ships // its own entity classes, so no server profile / data pack is written. - if (!settings.modelOnly) { - serverProfile = buildServerProfile(settings); + const serverProfile = settings.modelOnly ? null : buildServerProfile( + settings); + stampVersion(serverProfile, renderProfile); + if (serverProfile) { files.push({ path: paths.profile, content: toJson(serverProfile), binary: false @@ -111,15 +158,12 @@ function buildModProjectFiles(settings, options) { binary: false }, {path: paths.model, content: opts.modelBytes, binary: true}, - {path: paths.texture, content: opts.textureBytes, binary: true}); + ...textureFiles(settings, opts.textureResolution)); return {files, serverProfile, renderProfile}; } module.exports = { buildPackBundle, - buildModProjectFiles, - dataPaths, - datapackFiles, - resourcepackFiles + buildModProjectFiles }; diff --git a/plugins/easy_model_entities/src/builders/profileDiff.js b/plugins/easy_model_entities/src/builders/profileDiff.js index ebd09043..47c9b4b4 100644 --- a/plugins/easy_model_entities/src/builders/profileDiff.js +++ b/plugins/easy_model_entities/src/builders/profileDiff.js @@ -24,11 +24,10 @@ function valuesDiffer(a, b) { if (Array.isArray(a) || Array.isArray(b)) { return JSON.stringify(a) !== JSON.stringify(b); } + return a !== b; } -// Returns a new object containing only the keys whose value differs from the -// matching key in defaults. function diffFlat(values, defaults) { const result = {}; Object.keys(values).forEach((key) => { @@ -36,6 +35,7 @@ function diffFlat(values, defaults) { result[key] = values[key]; } }); + return result; } @@ -43,11 +43,10 @@ function isEmpty(object) { return !object || Object.keys(object).length === 0; } -// Assigns section to target under key only when it has at least one entry. function assignIfPresent(target, key, section) { if (!isEmpty(section)) { target[key] = section; } } -module.exports = {valuesDiffer, diffFlat, isEmpty, assignIfPresent}; +module.exports = {diffFlat, assignIfPresent}; diff --git a/plugins/easy_model_entities/src/builders/readme.js b/plugins/easy_model_entities/src/builders/readme.js index 90606fea..f3b71d6a 100644 --- a/plugins/easy_model_entities/src/builders/readme.js +++ b/plugins/easy_model_entities/src/builders/readme.js @@ -20,18 +20,25 @@ const {ResourceLocation} = require('../utils/ResourceLocation'); const TEMPLATE = require('../resources/readme.md'); -function buildReadme(settings) { +function buildReadme(settings, fileNames) { const id = ResourceLocation.buildResourceLocation(settings.namespace, settings.profileId); const modelType = settings.modelType || 'entity'; const serverProfileId = ResourceLocation.buildResourceLocation( settings.namespace, `${modelType}/${settings.profileId}`); + const names = fileNames || { + datapack: `${settings.profileId}_datapack.zip`, + resourcepack: `${settings.profileId}_resourcepack.zip` + }; + return TEMPLATE .replaceAll('{{id}}', id) .replaceAll('{{serverProfileId}}', serverProfileId) .replaceAll('{{mcVersion}}', `Minecraft: Java Edition ${settings.targetVersion}`) - .replaceAll('{{folderName}}', `${settings.namespace}_eme`); + .replaceAll('{{folderName}}', `${settings.namespace}_eme`) + .replaceAll('{{datapackFile}}', names.datapack) + .replaceAll('{{resourcepackFile}}', names.resourcepack); } module.exports = {buildReadme}; diff --git a/plugins/easy_model_entities/src/builders/renderProfile.js b/plugins/easy_model_entities/src/builders/renderProfile.js index 31fda4c7..0b226a59 100644 --- a/plugins/easy_model_entities/src/builders/renderProfile.js +++ b/plugins/easy_model_entities/src/builders/renderProfile.js @@ -20,7 +20,7 @@ const { bodyType, animationMode, - presetRenderBounds, + presetShadowRadius, isCustom, MODEL_TYPE_BLOCK_ENTITY } = require('../model/presetTypes'); @@ -33,29 +33,13 @@ function renderPresetType(settings) { ? 'static' : settings.presetType; } -function buildModelReference(settings) { - return `${settings.namespace}:easy_model_entities/models/${settings.profileId}`; -} - -function buildTextureReference(settings) { - return `${settings.namespace}:textures/entity/${settings.profileId}.png`; -} - function buildRendering(settings) { - const [boundsWidth, boundsHeight, boundsOffsetY, shadowRadius] = - presetRenderBounds(renderPresetType(settings)); return diffFlat({ scale: settings.rendering.scale, - shadow_radius: settings.rendering.shadowRadius, - visible_bounds_width: settings.rendering.visibleBoundsWidth, - visible_bounds_height: settings.rendering.visibleBoundsHeight, - visible_bounds_offset: settings.rendering.visibleBoundsOffset.slice() + shadow_radius: settings.rendering.shadowRadius }, { scale: 1, - shadow_radius: shadowRadius, - visible_bounds_width: boundsWidth, - visible_bounds_height: boundsHeight, - visible_bounds_offset: [0, boundsOffsetY, 0] + shadow_radius: presetShadowRadius(renderPresetType(settings)) }); } @@ -75,10 +59,11 @@ function buildAnimation(settings) { delete animation.swing_speed; delete animation.walk_speed_multiplier; } + return animation; } -function buildRenderProfile(settings) { +function buildRenderProfile(settings, textureResolution) { const renderPreset = renderPresetType(settings); const custom = settings.modelType !== MODEL_TYPE_BLOCK_ENTITY && isCustom(settings.presetType); @@ -86,17 +71,21 @@ function buildRenderProfile(settings) { schema_version: settings.schemaVersion, preset_type: renderPreset }; - if (settings.version) { - profile.version = settings.version; - } if (custom || settings.host.bodyType !== bodyType(renderPreset)) { profile.body_type = settings.host.bodyType; } - // model and texture follow the mod's conventional default path - // (namespace:easy_model_entities/models/id and namespace:textures/entity/id.png), - // so they are omitted and resolved automatically by the mod. + // The model and the index-0 texture follow the mod's conventional default + // path (namespace:easy_model_entities/models/id and + // namespace:textures/entity/id.png), so they are omitted whenever they match + // the default. Only deviating texture locations (e.g. vanilla index 0 or any + // index > 0) are spelled out for the mod's ModelTextureResolver. + if (textureResolution && textureResolution.texture) { + profile.texture = textureResolution.texture; + } + assignIfPresent(profile, 'textures', + textureResolution && textureResolution.textures); assignIfPresent(profile, 'rendering', buildRendering(settings)); assignIfPresent(profile, 'animation', buildAnimation(settings)); @@ -105,7 +94,5 @@ function buildRenderProfile(settings) { } module.exports = { - buildRenderProfile, - buildModelReference, - buildTextureReference + buildRenderProfile }; diff --git a/plugins/easy_model_entities/src/builders/serverProfile.js b/plugins/easy_model_entities/src/builders/serverProfile.js index 4b84a804..49dd11ca 100644 --- a/plugins/easy_model_entities/src/builders/serverProfile.js +++ b/plugins/easy_model_entities/src/builders/serverProfile.js @@ -33,12 +33,10 @@ const DEFAULT_FOLLOW_RANGE = 16; function effectiveDefaults(settings) { const preset = settings.presetType; - const move = settings.host.movementType; - const movement = movementDefaults(preset, move); - const mode = behaviorModeFor(preset, move); - const dimensions = settings.modelType === MODEL_TYPE_BLOCK_ENTITY - ? presetDefaults(preset, MODEL_TYPE_BLOCK_ENTITY).dimensions - : presetDimensions(preset); + const movementType = settings.host.movementType; + const movement = movementDefaults(preset, movementType); + const mode = behaviorModeFor(preset, movementType); + return { movement: { speed: movement.speed, @@ -48,14 +46,16 @@ function effectiveDefaults(settings) { behavior: { mode: mode, look_at_players: mode === 'idle_only' || mode === 'ambient', - random_stroll: move === 'ground' && mode === 'ambient' + random_stroll: movementType === 'ground' && mode === 'ambient' }, attributes: { max_health: DEFAULT_MAX_HEALTH, movement_speed: settings.movement.speed, follow_range: DEFAULT_FOLLOW_RANGE }, - dimensions: dimensions + dimensions: settings.modelType === MODEL_TYPE_BLOCK_ENTITY + ? presetDefaults(preset, MODEL_TYPE_BLOCK_ENTITY).dimensions + : presetDimensions(preset) }; } @@ -66,7 +66,9 @@ function buildEntity(settings, custom) { if (!custom) { return {}; } + const host = settings.host; + return { type: host.entityType, movement_type: host.movementType, @@ -83,6 +85,7 @@ function buildDimensions(settings, defaults, custom) { if (custom) { return values; } + return diffFlat(values, { width: defaults.dimensions.width, height: defaults.dimensions.height, @@ -101,9 +104,6 @@ function buildServerProfile(settings) { model_type: modelType, preset_type: settings.presetType }; - if (settings.version) { - profile.version = settings.version; - } // The server profile lives in a "/" subfolder, so its derived // id differs from the flat render profile id. The render profile must be named @@ -119,7 +119,6 @@ function buildServerProfile(settings) { assignIfPresent(profile, 'dimensions', buildDimensions(settings, defaults, custom)); - // Block entities ignore movement, behavior and attributes. if (!blockEntity) { assignIfPresent(profile, 'movement', diffFlat({ speed: settings.movement.speed, diff --git a/plugins/easy_model_entities/src/format/EmeCodecHooks.js b/plugins/easy_model_entities/src/format/EmeCodecHooks.js new file mode 100644 index 00000000..0576772a --- /dev/null +++ b/plugins/easy_model_entities/src/format/EmeCodecHooks.js @@ -0,0 +1,58 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {BlockbenchAdapter} = require('../BlockbenchAdapter'); +const {FORMAT_ID} = require('./EmeFormat'); + +const EME_SETTINGS_KEY = 'emeSettings'; + +function handleCompile(event) { + if (Format?.id !== FORMAT_ID) { + return; + } + + const settings = BlockbenchAdapter.loadSettings(); + if (settings) { + event.model[EME_SETTINGS_KEY] = settings; + } +} + +function handleParse(event) { + const formatId = event.model?.meta?.model_format || event.model?.model_format; + if (formatId !== FORMAT_ID) { + return; + } + + const settings = event.model[EME_SETTINGS_KEY]; + if (settings && typeof settings === 'object') { + BlockbenchAdapter.saveSettings(settings); + } +} + +function registerEmeCodecHooks() { + Codecs.project.on('compile', handleCompile); + Codecs.project.on('parse', handleParse); +} + +function unregisterEmeCodecHooks() { + Codecs.project.events?.compile?.remove?.(handleCompile); + Codecs.project.events?.parse?.remove?.(handleParse); +} + +module.exports = {registerEmeCodecHooks, unregisterEmeCodecHooks}; diff --git a/plugins/easy_model_entities/src/format/EmeFormat.js b/plugins/easy_model_entities/src/format/EmeFormat.js new file mode 100644 index 00000000..61253ca9 --- /dev/null +++ b/plugins/easy_model_entities/src/format/EmeFormat.js @@ -0,0 +1,72 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const FORMAT_ID = 'eme_entity'; + +let emeFormat = null; + +function toModelIdentifier(name) { + return name.trim().toLowerCase() + .replace(/\s+/g, '_') + .replace(/[^a-z0-9_]/g, '') + .replace(/_+/g, '_') + .replace(/^_|_$/g, ''); +} + +function registerEmeFormat(icon) { + if (emeFormat) { + return; + } + + emeFormat = new ModelFormat(FORMAT_ID, { + name: 'Easy Model Entity', + category: 'minecraft', + description: 'Entity model for the Easy Model Entities mod (Minecraft: Java Edition).', + icon: icon || 'pets', + rotate_cubes: true, + box_uv: true, + optional_box_uv: true, + single_texture: false, + bone_rig: true, + centered_grid: true, + animated_textures: false, + animation_mode: true, + animation_files: false, + java_edition_mapping: false, + model_identifier: true, + locators: true, + codec: Codecs.project, + onActivation() { + if (Project && !Project.model_identifier && Project.name) { + Project.model_identifier = toModelIdentifier(Project.name); + } + }, + onDeactivation() { + }, + }); +} + +function unregisterEmeFormat() { + if (emeFormat) { + emeFormat.delete(); + emeFormat = null; + } +} + +module.exports = {FORMAT_ID, registerEmeFormat, unregisterEmeFormat}; diff --git a/plugins/easy_model_entities/src/i18n/translations.js b/plugins/easy_model_entities/src/i18n/translations.js index 87e506ab..3c748a6f 100644 --- a/plugins/easy_model_entities/src/i18n/translations.js +++ b/plugins/easy_model_entities/src/i18n/translations.js @@ -26,7 +26,6 @@ const EN = { 'eme.field.preset': 'Preset', 'eme.field.namespace': 'Namespace (mod id)', 'eme.field.profileId': 'Profile ID', - 'eme.field.version': 'Version (optional)', 'eme.field.targetVersion': 'Minecraft Version', 'eme.field.exportType': 'Export Type', 'eme.field.modelType': 'Type', @@ -46,9 +45,6 @@ const EN = { 'eme.field.followRange': 'Follow Range', 'eme.field.scale': 'Scale', 'eme.field.shadowRadius': 'Shadow Radius', - 'eme.field.visibleBoundsWidth': 'Visible Bounds Width', - 'eme.field.visibleBoundsHeight': 'Visible Bounds Height', - 'eme.field.visibleBoundsOffset': 'Visible Bounds Offset', 'eme.field.animationMode': 'Animation Mode', 'eme.field.swingSpeed': 'Swing Speed', 'eme.field.walkSpeedMultiplier': 'Walk Speed Multiplier', @@ -114,7 +110,6 @@ const DE = { 'eme.field.preset': 'Vorlage', 'eme.field.namespace': 'Namespace (Mod-ID)', 'eme.field.profileId': 'Profil-ID', - 'eme.field.version': 'Version (optional)', 'eme.field.targetVersion': 'Minecraft-Version', 'eme.field.exportType': 'Export-Typ', 'eme.field.modelType': 'Typ', @@ -134,9 +129,6 @@ const DE = { 'eme.field.followRange': 'Folgereichweite', 'eme.field.scale': 'Skalierung', 'eme.field.shadowRadius': 'Schattenradius', - 'eme.field.visibleBoundsWidth': 'Sichtbarkeitsbox-Breite', - 'eme.field.visibleBoundsHeight': 'Sichtbarkeitsbox-Höhe', - 'eme.field.visibleBoundsOffset': 'Sichtbarkeitsbox-Versatz', 'eme.field.animationMode': 'Animationsmodus', 'eme.field.swingSpeed': 'Schwunggeschwindigkeit', 'eme.field.walkSpeedMultiplier': 'Geh-Geschwindigkeitsfaktor', @@ -213,6 +205,7 @@ function t(key) { return translated; } } + return EN[key] || key; } diff --git a/plugins/easy_model_entities/src/index.js b/plugins/easy_model_entities/src/index.js index f178a156..b14ddc80 100644 --- a/plugins/easy_model_entities/src/index.js +++ b/plugins/easy_model_entities/src/index.js @@ -26,17 +26,25 @@ const { const {ModelDimensions} = require('./model/ModelDimensions'); const {PresetDetector} = require('./model/PresetDetector'); const {ResourceLocation} = require('./utils/ResourceLocation'); +const {resolveTextures} = require('./utils/TextureResolver'); const {Validator} = require('./model/Validator'); const {buildPackBundle, buildModProjectFiles} = require('./builders/exporter'); const {BlockbenchAdapter} = require('./BlockbenchAdapter'); const {openExportDialog} = require('./ui/exportDialog'); const {registerTranslations, t} = require('./i18n/translations'); -const actionIconSvg = require('./resources/action_icon.svg'); +const exportIconSvg = require('./resources/export_icon.svg'); +const createIconSvg = require('./resources/create_icon.svg'); +const {FORMAT_ID, registerEmeFormat, unregisterEmeFormat} = require( + './format/EmeFormat'); +const {registerEmeCodecHooks, unregisterEmeCodecHooks} = require( + './format/EmeCodecHooks'); +const MODDED_ENTITY_FORMAT_ID = 'modded_entity'; const ACTION_ID = 'eme_export'; const SETTING_ID = 'eme_enable_customization'; const EXPERIMENTAL_SETTING_ID = 'eme_enable_experimental'; -const ACTION_ICON = 'data:image/svg+xml,' + encodeURIComponent(actionIconSvg); +const EXPORT_ICON = 'data:image/svg+xml,' + encodeURIComponent(exportIconSvg); +const CREATE_ICON = 'data:image/svg+xml,' + encodeURIComponent(createIconSvg); let exportAction; let projectProperty; let customizationSetting; @@ -46,6 +54,7 @@ function customizationEnabled() { if (customizationSetting && typeof customizationSetting.value === 'boolean') { return customizationSetting.value; } + return false; } @@ -53,6 +62,7 @@ function experimentalEnabled() { if (experimentalSetting && typeof experimentalSetting.value === 'boolean') { return experimentalSetting.value; } + return false; } @@ -91,17 +101,21 @@ function resolveDialogState() { ModelDimensions.deriveDimensions(bounds, settings.host.bodyType)); customize = false; exportType = 'packs'; - const projectName = BlockbenchAdapter.getProjectName(); - if (projectName) { - settings.profileId = ResourceLocation.sanitizeProfileId(projectName); - } } - const modelDimensions = ModelDimensions.deriveDimensions(bounds, - settings.host.bodyType); + // Derive the profile id from the model name/file whenever none has been set + // yet (empty or still the placeholder default). + const projectName = BlockbenchAdapter.getProjectName(); + if (projectName + && (!settings.profileId || settings.profileId + === getDefaults().profileId)) { + settings.profileId = ResourceLocation.sanitizeProfileId(projectName); + } + return { settings, preset, blockPreset, modelType, customize, exportType, - modelDimensions + modelDimensions: ModelDimensions.deriveDimensions(bounds, + settings.host.bodyType) }; } @@ -110,16 +124,13 @@ function formatIssues(issues) { } function collectContext() { - const stats = BlockbenchAdapter.getModelStats(); - return Object.assign({}, stats, { - textureCount: BlockbenchAdapter.getTextureCount(), - visibleBoundsManual: true - }); + return Object.assign({}, BlockbenchAdapter.getModelStats()); } function runExport(settings, target) { - const context = collectContext(); - const result = Validator.validateSettings(settings, context); + const textureResolution = resolveTextures( + BlockbenchAdapter.collectTextures(), settings); + const result = Validator.validateSettings(settings, collectContext()); if (!result.valid) { Blockbench.showMessageBox({ @@ -130,7 +141,7 @@ function runExport(settings, target) { return; } - const doExport = () => performExport(settings, target); + const doExport = () => performExport(settings, target, textureResolution); if (result.warnings.length > 0) { Blockbench.showMessageBox( @@ -153,10 +164,10 @@ function runExport(settings, target) { } } -function performExport(settings, target) { +function performExport(settings, target, textureResolution) { const options = { modelBytes: BlockbenchAdapter.getModelBytes(), - textureBytes: BlockbenchAdapter.getTextureBytes() + textureResolution }; BlockbenchAdapter.saveSettings(settings); @@ -169,8 +180,8 @@ function performExport(settings, target) { } function exportToZip(settings, options) { - const bundle = buildPackBundle(settings, options); - BlockbenchAdapter.exportPackBundle(bundle, + BlockbenchAdapter.exportPackBundle( + buildPackBundle(settings, options), `${settings.namespace}_${settings.profileId}_eme`) .then(() => { Blockbench.showQuickMessage('Easy Model Entities packs exported', 1500); @@ -227,6 +238,7 @@ function openDialog() { Blockbench.showQuickMessage('Open a project first', 1500); return; } + const state = resolveDialogState(); openExportDialog({ settings: state.settings, @@ -247,16 +259,22 @@ BBPlugin.register('easy_model_entities', { icon: 'icon.png', author: 'Markus Bordihn', description: 'Export Blockbench models for the Easy Model Entities mod (Minecraft: Java Edition) as ready-to-install Data Pack and Resource Pack files.', - tags: ['Minecraft: Java Edition', 'Exporter', 'Entity'], + tags: ['Minecraft: Java Edition', 'Format', 'Exporter', 'Entity'], version: '0.1.0', min_version: '4.9.0', variant: 'desktop', + await_loading: true, has_changelog: true, - website: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities', + contributes: { + formats: ['eme_entity'] + }, + website: 'https://www.curseforge.com/minecraft/mc-mods/easy-model-entities', repository: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities', bug_tracker: 'https://github.com/MarkusBordihn/BOs-Easy-Model-Entities/issues', onload() { registerTranslations(); + registerEmeFormat(CREATE_ICON); + registerEmeCodecHooks(); projectProperty = new Property(ModelProject, 'object', BlockbenchAdapter.PROJECT_PROPERTY, { @@ -280,9 +298,11 @@ BBPlugin.register('easy_model_entities', { }); exportAction = new Action(ACTION_ID, { - name: 'Export Easy Model Entities Pack', + name: 'Export Easy Model Entity Pack', description: 'Export the current project as Easy Model Entities Data Pack and Resource Pack.', - icon: ACTION_ICON, + icon: EXPORT_ICON, + condition: () => typeof Format !== 'undefined' && Format + && (Format.id === FORMAT_ID || Format.id === MODDED_ENTITY_FORMAT_ID), click() { openDialog(); } @@ -291,6 +311,8 @@ BBPlugin.register('easy_model_entities', { MenuBar.addAction(exportAction, 'file.export'); }, onunload() { + unregisterEmeCodecHooks(); + unregisterEmeFormat(); if (exportAction) { exportAction.delete(); } diff --git a/plugins/easy_model_entities/src/model/ModelDimensions.js b/plugins/easy_model_entities/src/model/ModelDimensions.js index cba7a9f4..6b26282f 100644 --- a/plugins/easy_model_entities/src/model/ModelDimensions.js +++ b/plugins/easy_model_entities/src/model/ModelDimensions.js @@ -22,14 +22,10 @@ class ModelDimensions { static FALLBACK = { width: 0.6, height: 0.8, - eyeHeight: 0.5, - visibleBoundsWidth: 1.2, - visibleBoundsHeight: 1, - visibleBoundsOffset: [0, 0.5, 0] + eyeHeight: 0.5 }; static #EYE_HEIGHT_FACTOR = {biped: 0.9, quadruped: 0.8, static: 0.85}; - static #BOUNDS_MARGIN = 1.1; static #round(value, digits) { const factor = Math.pow(10, digits == null ? 3 : digits); @@ -47,42 +43,27 @@ class ModelDimensions { static deriveDimensions(bounds, bodyType) { if (!ModelDimensions.#isUsableBounds(bounds)) { - return { - ...ModelDimensions.FALLBACK, - visibleBoundsOffset: ModelDimensions.FALLBACK.visibleBoundsOffset.slice() - }; + return {...ModelDimensions.FALLBACK}; } - const extentX = Math.max(0, bounds.maxX - bounds.minX) - / ModelDimensions.PIXELS_PER_BLOCK; - const extentY = Math.max(0, bounds.maxY - bounds.minY) - / ModelDimensions.PIXELS_PER_BLOCK; - const extentZ = Math.max(0, bounds.maxZ - bounds.minZ) + const width = Math.max( + Math.max(0, bounds.maxX - bounds.minX), + Math.max(0, bounds.maxZ - bounds.minZ) + ) / ModelDimensions.PIXELS_PER_BLOCK; + const height = Math.max(0, bounds.maxY - bounds.minY) / ModelDimensions.PIXELS_PER_BLOCK; - const width = Math.max(extentX, extentZ); - const height = extentY; if (width <= 0 || height <= 0) { - return { - ...ModelDimensions.FALLBACK, - visibleBoundsOffset: ModelDimensions.FALLBACK.visibleBoundsOffset.slice() - }; + return {...ModelDimensions.FALLBACK}; } - const eyeHeightFactor = - ModelDimensions.#EYE_HEIGHT_FACTOR[bodyType] == null - ? ModelDimensions.#EYE_HEIGHT_FACTOR.static - : ModelDimensions.#EYE_HEIGHT_FACTOR[bodyType]; + const eyeHeightFactor = ModelDimensions.#EYE_HEIGHT_FACTOR[bodyType] + ?? ModelDimensions.#EYE_HEIGHT_FACTOR.static; return { width: ModelDimensions.#round(width), height: ModelDimensions.#round(height), - eyeHeight: ModelDimensions.#round(height * eyeHeightFactor), - visibleBoundsWidth: ModelDimensions.#round( - width * ModelDimensions.#BOUNDS_MARGIN), - visibleBoundsHeight: ModelDimensions.#round( - height * ModelDimensions.#BOUNDS_MARGIN), - visibleBoundsOffset: [0, ModelDimensions.#round(height / 2), 0] + eyeHeight: ModelDimensions.#round(height * eyeHeightFactor) }; } @@ -95,9 +76,6 @@ class ModelDimensions { settings.dimensions.width = modelDimensions.width; settings.dimensions.height = modelDimensions.height; settings.dimensions.eyeHeight = modelDimensions.eyeHeight; - settings.rendering.visibleBoundsWidth = modelDimensions.visibleBoundsWidth; - settings.rendering.visibleBoundsHeight = modelDimensions.visibleBoundsHeight; - settings.rendering.visibleBoundsOffset = modelDimensions.visibleBoundsOffset.slice(); return settings; } } diff --git a/plugins/easy_model_entities/src/model/PresetDetector.js b/plugins/easy_model_entities/src/model/PresetDetector.js index 658dbbc1..e93ecc7b 100644 --- a/plugins/easy_model_entities/src/model/PresetDetector.js +++ b/plugins/easy_model_entities/src/model/PresetDetector.js @@ -43,10 +43,12 @@ class PresetDetector { if (!bounds) { return null; } - const keys = ['minX', 'minY', 'minZ', 'maxX', 'maxY', 'maxZ']; - if (!keys.every((key) => Number.isFinite(bounds[key]))) { + + if (!['minX', 'minY', 'minZ', 'maxX', 'maxY', 'maxZ'].every( + (key) => Number.isFinite(bounds[key]))) { return null; } + return { x: Math.max(0, bounds.maxX - bounds.minX), y: Math.max(0, bounds.maxY - bounds.minY), @@ -54,7 +56,6 @@ class PresetDetector { }; } - // Returns {presetType, reason}. static detect(stats, bounds) { const names = PresetDetector.#names(stats); const hasWings = PresetDetector.#any(names, 'wing'); @@ -89,9 +90,8 @@ class PresetDetector { const extents = PresetDetector.#extents(bounds); if (extents && extents.x > 0 && extents.y > 0 && extents.z > 0) { - const max = Math.max(extents.x, extents.y, extents.z); - const min = Math.min(extents.x, extents.y, extents.z); - if (max / min <= PresetDetector.#CUBE_RATIO) { + if (Math.max(extents.x, extents.y, extents.z) / Math.min(extents.x, + extents.y, extents.z) <= PresetDetector.#CUBE_RATIO) { return {presetType: 'cuboid_still', reason: 'cube-like proportions'}; } if (extents.y < Math.max(extents.x, extents.z) * 0.5) { diff --git a/plugins/easy_model_entities/src/model/Validator.js b/plugins/easy_model_entities/src/model/Validator.js index bd129e47..355865f7 100644 --- a/plugins/easy_model_entities/src/model/Validator.js +++ b/plugins/easy_model_entities/src/model/Validator.js @@ -53,6 +53,7 @@ class Validator { }); return false; } + return true; } @@ -150,13 +151,6 @@ class Validator { {code: 'MISSING_TEXTURE', message: 'No texture present in project'}); } - if (Validator.#isFiniteNumber(ctx.textureCount) && ctx.textureCount > 1) { - warnings.push({ - code: 'MULTIPLE_TEXTURES', - message: 'Project has multiple textures; only the active texture is exported' - }); - } - if (Validator.#isFiniteNumber(ctx.textureWidth) && Validator.#isFiniteNumber(ctx.textureHeight)) { if (ctx.textureWidth > Validator.BUDGETS.maxTextureSize @@ -194,22 +188,14 @@ class Validator { }); } - const missingParts = Validator.getMissingBodyParts(settings.host.bodyType, - ctx.boneNames || []); - missingParts.forEach((part) => { + Validator.getMissingBodyParts(settings.host.bodyType, + ctx.boneNames || []).forEach((part) => { warnings.push({ code: 'MISSING_BODY_PART', message: `Missing recommended body part: ${part}` }); }); - if (ctx.visibleBoundsManual === false) { - warnings.push({ - code: 'VISIBLE_BOUNDS_DEFAULT', - message: 'Visible bounds were not set manually' - }); - } - return {errors, warnings, valid: errors.length === 0}; } @@ -217,6 +203,7 @@ class Validator { const required = Validator.REQUIRED_BODY_PARTS[bodyType] || []; const present = new Set( (boneNames || []).map((name) => String(name).toLowerCase())); + return required.filter((part) => !present.has(part)); } @@ -228,6 +215,7 @@ class Validator { message: 'Empty output path' }; } + const normalized = relativePath.replaceAll('\\', '/'); if (normalized.includes('..')) { return { @@ -236,6 +224,7 @@ class Validator { message: 'Path traversal detected' }; } + if (/^([a-zA-Z]:\/|\/)/.test(normalized)) { return { valid: false, @@ -243,6 +232,7 @@ class Validator { message: 'Absolute paths are not allowed' }; } + return {valid: true, root: outputRoot, path: normalized}; } } diff --git a/plugins/easy_model_entities/src/model/presetTypes.js b/plugins/easy_model_entities/src/model/presetTypes.js index 967c01c5..c15c7e28 100644 --- a/plugins/easy_model_entities/src/model/presetTypes.js +++ b/plugins/easy_model_entities/src/model/presetTypes.js @@ -165,30 +165,28 @@ function presetDimensions(presetType) { } } -// [visibleBoundsWidth, visibleBoundsHeight, visibleBoundsOffsetY, shadowRadius] -function presetRenderBounds(presetType) { +// Shadow radius the mod derives per render preset (ModelRenderSettings). +function presetShadowRadius(presetType) { switch (presetType) { case 'quadruped_still': case 'quadruped_wandering': - return [0.9, 0.9, 0.45, 0.45]; + return 0.45; case 'aquatic_still': case 'aquatic_swimming': - return [0.7, 0.4, 0.2, 0.25]; case 'winged_still': case 'winged_wandering': - return [0.6, 0.9, 0.45, 0.25]; case 'winged_humanoid_still': case 'winged_humanoid_wandering': - return [0.6, 0.8, 0.4, 0.25]; + return 0.25; case 'arthropod_still': case 'arthropod_wandering': - return [1.4, 0.9, 0.45, 0.7]; + return 0.7; case 'cuboid_still': case 'cuboid_hopping': case 'floating_still': - return [1.0, 1.0, 0.5, 0.5]; + return 0.5; default: - return [0.6, 1.8, 0.9, 0.3]; + return 0.3; } } @@ -198,12 +196,15 @@ function behaviorModeFor(presetType, move) { if (isMoving(presetType)) { return 'ambient'; } + if (isStill(presetType)) { return 'idle_only'; } + if (presetType === 'custom') { return move === 'ground' ? 'idle_only' : 'static'; } + return 'static'; } @@ -239,25 +240,18 @@ function blockEntityAnimationMode(presetType) { } // Block entities ignore movement/behavior/attributes; the host is always a -// static cuboid block. Render bounds default to a 1x1x1 block. +// static cuboid block. function blockEntityPresetDefaults(presetType) { return { schemaVersion: SCHEMA_VERSION, modelType: MODEL_TYPE_BLOCK_ENTITY, presetType: presetType, - version: '', host: {entityType: '', movementType: 'static', bodyType: 'static'}, dimensions: {...BLOCK_ENTITY_DIMENSIONS}, movement: {speed: 0, stepHeight: 0, gravity: false}, behavior: {mode: 'static', lookAtPlayers: false, randomStroll: false}, attributes: {maxHealth: 10, movementSpeed: 0, followRange: 16}, - rendering: { - scale: 1, - shadowRadius: 0.5, - visibleBoundsWidth: 1.0, - visibleBoundsHeight: 1.0, - visibleBoundsOffset: [0, 0.5, 0] - }, + rendering: {scale: 1, shadowRadius: 0.5}, animation: { mode: blockEntityAnimationMode(presetType), swingSpeed: 1, @@ -266,48 +260,38 @@ function blockEntityPresetDefaults(presetType) { }; } -// Full settings object matching the mod defaults for the given preset type. function presetDefaults(presetType, modelType) { if (modelType === MODEL_TYPE_BLOCK_ENTITY) { return blockEntityPresetDefaults(presetType); } + const move = movementType(presetType); const ground = move === 'ground'; const speed = ground ? 0.22 : 0; - const stepHeight = ground ? 0.6 : 0; - const gravity = presetType === 'static' || ground; const mode = behaviorMode(presetType); - const lookAtPlayers = mode === 'idle_only' || mode === 'ambient'; - const randomStroll = ground && mode === 'ambient'; - const dimensions = presetDimensions(presetType); - const [boundsWidth, boundsHeight, boundsOffsetY, shadowRadius] = - presetRenderBounds(presetType); return { schemaVersion: SCHEMA_VERSION, modelType: MODEL_TYPE_ENTITY, presetType: presetType, - version: '', host: { entityType: entityType(presetType), movementType: move, bodyType: bodyType(presetType) }, - dimensions: dimensions, - movement: {speed: speed, stepHeight: stepHeight, gravity: gravity}, + dimensions: presetDimensions(presetType), + movement: { + speed: speed, + stepHeight: ground ? 0.6 : 0, + gravity: presetType === 'static' || ground + }, behavior: { mode: mode, - lookAtPlayers: lookAtPlayers, - randomStroll: randomStroll + lookAtPlayers: mode === 'idle_only' || mode === 'ambient', + randomStroll: ground && mode === 'ambient' }, attributes: {maxHealth: 10, movementSpeed: speed, followRange: 16}, - rendering: { - scale: 1, - shadowRadius: shadowRadius, - visibleBoundsWidth: boundsWidth, - visibleBoundsHeight: boundsHeight, - visibleBoundsOffset: [0, boundsOffsetY, 0] - }, + rendering: {scale: 1, shadowRadius: presetShadowRadius(presetType)}, animation: { mode: animationMode(presetType), swingSpeed: 1, @@ -320,27 +304,16 @@ module.exports = { SCHEMA_VERSION, MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY, - MODEL_TYPES, - GROUND_ENTITY, - STATIC_ENTITY, PRESET_TYPES, BLOCK_ENTITY_PRESET_TYPES, SELECTABLE_PRESET_TYPES, - STABLE_PRESET_TYPES, isStablePreset, isCustom, - isStill, - isMoving, bodyType, - movementType, - entityType, - behaviorMode, behaviorModeFor, movementDefaults, animationMode, - blockEntityAnimationMode, presetDimensions, - presetRenderBounds, - presetDefaults, - blockEntityPresetDefaults + presetShadowRadius, + presetDefaults }; diff --git a/plugins/easy_model_entities/src/model/templates.js b/plugins/easy_model_entities/src/model/templates.js index 49e24e4b..853b6e53 100644 --- a/plugins/easy_model_entities/src/model/templates.js +++ b/plugins/easy_model_entities/src/model/templates.js @@ -18,9 +18,7 @@ */ const { - SCHEMA_VERSION, PRESET_TYPES, - BLOCK_ENTITY_PRESET_TYPES, SELECTABLE_PRESET_TYPES, MODEL_TYPE_ENTITY, presetDefaults @@ -42,6 +40,7 @@ function deepMerge(target, source) { result[key] = value; } }); + return result; } @@ -62,13 +61,10 @@ function applyTemplate(presetType, modelType) { } module.exports = { - SCHEMA_VERSION, DEFAULT_PRESET, PRESET_TYPES, - BLOCK_ENTITY_PRESET_TYPES, SELECTABLE_PRESET_TYPES, getDefaults, applyTemplate, - presetDefaults, deepMerge }; diff --git a/plugins/easy_model_entities/src/resources/action_icon.svg b/plugins/easy_model_entities/src/resources/action_icon.svg deleted file mode 100644 index a67dd1ec..00000000 --- a/plugins/easy_model_entities/src/resources/action_icon.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/plugins/easy_model_entities/src/resources/create_icon.svg b/plugins/easy_model_entities/src/resources/create_icon.svg new file mode 100644 index 00000000..3c75f629 --- /dev/null +++ b/plugins/easy_model_entities/src/resources/create_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/easy_model_entities/src/resources/export_icon.svg b/plugins/easy_model_entities/src/resources/export_icon.svg new file mode 100644 index 00000000..29210322 --- /dev/null +++ b/plugins/easy_model_entities/src/resources/export_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/easy_model_entities/src/resources/readme.md b/plugins/easy_model_entities/src/resources/readme.md index c32ed7a7..bdf079b6 100644 --- a/plugins/easy_model_entities/src/resources/readme.md +++ b/plugins/easy_model_entities/src/resources/readme.md @@ -6,18 +6,32 @@ mod on **{{mcVersion}}**. > **Beta:** The exporter is still in beta. The pack format may change in a future version, so you > might need to re-export this pack after updating the plugin or the mod. -This archive contains two ready-to-use packs that belong together: +This archive contains two ready-to-use packs that belong together. Their file names are prefixed +with the model id so several exported packs can live in the same folder without overwriting each +other: -- `datapack.zip`: the server-side entity profile (gameplay data) -- `resourcepack.zip`: the model, texture and render profile (visuals) +- `{{datapackFile}}`: the server-side entity profile (gameplay data) +- `{{resourcepackFile}}`: the model, texture and render profile (visuals) Profile ID: `{{serverProfileId}}` +## Model Format + +This pack was exported using the **Easy Model Entity** format (`eme_entity`), which is provided by +this plugin and tailored specifically for the Easy Model Entities mod. If you need to re-edit the +model, open it in Blockbench and select **Easy Model Entity** as the format. Alternatively, the +standard **Modded Entity** format (built into Blockbench) is also supported and can be exported +with the same plugin. + ## Requirements You need the **Easy Model Entities** mod installed for **{{mcVersion}}** (Forge or Fabric). -Without the mod these files do nothing. Get the mod from its distribution page (CurseForge / -Modrinth) and install it like any other mod before using this pack. +Without the mod these files do nothing. Get the mod from: + +- [CurseForge](https://www.curseforge.com/minecraft/mc-mods/easy-model-entities) +- [Modrinth](https://modrinth.com/mod/easy-model-entities) + +Install it like any other mod before using this pack. ## Installation @@ -25,7 +39,7 @@ You do not need to unpack anything — just move the two ZIP files into the righ ### 1. Resource Pack (visuals, client side) -Move `resourcepack.zip` into your Minecraft resourcepacks folder: +Move `{{resourcepackFile}}` into your Minecraft resourcepacks folder: - Windows: `%appdata%\.minecraft\resourcepacks\` - Linux: `~/.minecraft/resourcepacks/` @@ -35,7 +49,7 @@ Then start Minecraft and enable the pack under **Options > Resource Packs**. ### 2. Data Pack (gameplay, world / server side) -Move `datapack.zip` into your world's `datapacks` folder: +Move `{{datapackFile}}` into your world's `datapacks` folder: - Single player: `.minecraft/saves//datapacks/` - Dedicated server: `/world/datapacks/` diff --git a/plugins/easy_model_entities/src/tests/exportDialog.test.js b/plugins/easy_model_entities/src/tests/exportDialog.test.js index 3ff9c9d5..4bfffb93 100644 --- a/plugins/easy_model_entities/src/tests/exportDialog.test.js +++ b/plugins/easy_model_entities/src/tests/exportDialog.test.js @@ -1,3 +1,22 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + const { settingsToForm, formToSettings, @@ -7,14 +26,7 @@ const { } = require('../ui/exportDialog'); const {fixtureSettings} = require('./fixtureData'); -const MODEL_DIMS = { - width: 1, - height: 2, - eyeHeight: 1.8, - visibleBoundsWidth: 1.1, - visibleBoundsHeight: 2.2, - visibleBoundsOffset: [0.0, 1, 0.0] -}; +const MODEL_DIMS = {width: 1, height: 2, eyeHeight: 1.8}; describe('exportDialog mapping', () => { test('settings survive a form round-trip', () => { @@ -76,7 +88,6 @@ describe('presetFormValues', () => { expect(values.animationMode).toBe('none'); expect(values.width).toBe(1); expect(values.height).toBe(2); - expect(values.visibleBoundsHeight).toBe(2.2); expect(values.namespace).toBeUndefined(); expect(values.profileId).toBeUndefined(); expect(values.targetVersion).toBeUndefined(); @@ -94,7 +105,6 @@ describe('resolveExportSettings', () => { preset: 'statue', namespace: 'example.org', profileId: 'statue', - version: 'v2', targetVersion: '1.20.1', customize: false, width: 999, @@ -108,7 +118,6 @@ describe('resolveExportSettings', () => { expect(settings.dimensions.height).toBe(2); expect(settings.namespace).toBe('example.org'); expect(settings.profileId).toBe('statue'); - expect(settings.version).toBe('v2'); }); test('custom preset keeps the stored base settings', () => { diff --git a/plugins/easy_model_entities/src/tests/exporter.test.js b/plugins/easy_model_entities/src/tests/exporter.test.js index 0f800a6b..075f9818 100644 --- a/plugins/easy_model_entities/src/tests/exporter.test.js +++ b/plugins/easy_model_entities/src/tests/exporter.test.js @@ -1,5 +1,25 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + const {buildPackBundle, buildModProjectFiles} = require('../builders/exporter'); const {applyTemplate} = require('../model/templates'); +const {resolveTextures} = require('../utils/TextureResolver'); const {fixtureSettings, fixtureExportOptions} = require('./fixtureData'); function fileByPath(files, filePath) { @@ -26,6 +46,11 @@ describe('buildPackBundle', () => { ].sort()); }); + test('exposes profile-id-prefixed inner pack file names', () => { + expect(bundle.datapackFileName).toBe('lizard_datapack.zip'); + expect(bundle.resourcepackFileName).toBe('lizard_resourcepack.zip'); + }); + test('README mentions the mod, Java Edition and both packs', () => { const readme = bundle.readme.content; expect(readme).toContain('Easy Model Entities'); @@ -69,7 +94,7 @@ describe('buildPackBundle', () => { expect(model.binary).toBe(true); expect(model.content).toBe(opts.modelBytes); expect(texture.binary).toBe(true); - expect(texture.content).toBe(opts.textureBytes); + expect(texture.content).toBe(opts.textureResolution.packed[0].bytes); }); }); @@ -107,8 +132,9 @@ describe('model-only export (mod integration)', () => { test('writes only render profile, model and texture (no server profile)', () => { - const {files, serverProfile} = buildModProjectFiles(modelOnlySettings(), - fixtureExportOptions()); + const settings = modelOnlySettings(); + const {files, serverProfile} = buildModProjectFiles(settings, + fixtureExportOptions(settings)); expect(serverProfile).toBeNull(); expect(files.map((f) => f.path)).toEqual([ 'assets/example/easy_model_entities/render_profiles/chestling.json', @@ -117,6 +143,82 @@ describe('model-only export (mod integration)', () => { ]); expect(files.some((f) => f.path.includes('/profiles/'))).toBe(false); }); + + test('leaves the render profile version-less (mod owns the contract)', () => { + const settings = modelOnlySettings(); + const {renderProfile} = buildModProjectFiles(settings, + fixtureExportOptions(settings)); + expect(renderProfile.version).toBeUndefined(); + }); +}); + +describe('version stamp (server-relevant content hash)', () => { + test('server and render profile share the same stamp', () => { + const bundle = buildPackBundle(fixtureSettings(), fixtureExportOptions()); + expect(bundle.serverProfile.version).toMatch(/^[0-9a-f]{8}$/); + expect(bundle.renderProfile.version).toBe(bundle.serverProfile.version); + }); + + test('a server-relevant change bumps the stamp', () => { + const base = buildPackBundle(fixtureSettings(), fixtureExportOptions()); + const changed = fixtureSettings(); + changed.dimensions.width += 1; + const next = buildPackBundle(changed, fixtureExportOptions()); + expect(next.serverProfile.version) + .not.toBe(base.serverProfile.version); + }); + + test('a texture-only change keeps the stamp stable', () => { + const settings = fixtureSettings(); + const base = buildPackBundle(settings, fixtureExportOptions()); + const extraTexture = resolveTextures([ + { + index: 0, namespace: '', folder: '', name: '', path: '', + bytes: Uint8Array.from([1]) + }, + { + index: 1, namespace: 'minecraft', folder: 'entity/chest', + name: 'normal.png', path: '', bytes: Uint8Array.from([1]) + } + ], settings); + const next = buildPackBundle(settings, + {modelBytes: 'different', textureResolution: extraTexture}); + expect(next.serverProfile.version).toBe(base.serverProfile.version); + }); +}); + +describe('mixed vanilla + custom textures', () => { + const PIXELS = Uint8Array.from([0x89, 0x50, 0x4e, 0x47]); + + function mixedOptions(settings) { + const textureResolution = resolveTextures([ + { + index: 0, namespace: 'minecraft', folder: 'entity/chest', + name: 'normal.png', path: '', bytes: PIXELS + }, + { + index: 1, namespace: '', folder: 'block', name: 'eye_texture', + path: '', bytes: PIXELS + } + ], settings); + return {modelBytes: '{}', textureResolution}; + } + + test('packs only the custom texture and references the vanilla one', () => { + const settings = fixtureSettings(); + const {files} = buildModProjectFiles(settings, mixedOptions(settings)); + const texturePaths = files.map((f) => f.path) + .filter((p) => p.includes('/textures/')); + expect(texturePaths).toEqual( + ['assets/example/textures/entity/lizard_1.png']); + + const render = JSON.parse(fileByPath(files, + 'assets/example/easy_model_entities/render_profiles/lizard.json') + .content); + expect(render.texture).toBe('minecraft:textures/entity/chest/normal.png'); + expect(render.textures).toEqual( + {1: 'example:textures/entity/lizard_1.png'}); + }); }); describe('block entity export uses the block_entity subfolder', () => { @@ -128,8 +230,9 @@ describe('block entity export uses the block_entity subfolder', () => { } test('server profile lands under profiles/block_entity', () => { - const {files} = buildModProjectFiles(blockSettings(), - fixtureExportOptions()); + const settings = blockSettings(); + const {files} = buildModProjectFiles(settings, + fixtureExportOptions(settings)); expect(files.map((f) => f.path)).toContain( 'data/example/easy_model_entities/profiles/block_entity/shrine.json'); }); diff --git a/plugins/easy_model_entities/src/tests/fixtureData.js b/plugins/easy_model_entities/src/tests/fixtureData.js index ce0bbd50..142001a1 100644 --- a/plugins/easy_model_entities/src/tests/fixtureData.js +++ b/plugins/easy_model_entities/src/tests/fixtureData.js @@ -18,6 +18,7 @@ */ const {applyTemplate} = require('../model/templates'); +const {resolveTextures} = require('../utils/TextureResolver'); const FIXTURE_MODEL_BYTES = '{"meta":{"format_version":"4.5"},"name":"lizard"}'; const FIXTURE_TEXTURE_BYTES = Uint8Array.from( @@ -27,13 +28,25 @@ function fixtureSettings() { const settings = applyTemplate('quadruped_wandering'); settings.namespace = 'example'; settings.profileId = 'lizard'; + return settings; } -function fixtureExportOptions() { +// A single custom texture at index 0, matching the legacy default path +// assets//textures/entity/.png. +function fixtureTextureResolution(settings) { + return resolveTextures([ + { + index: 0, name: '', namespace: '', folder: '', path: '', + bytes: FIXTURE_TEXTURE_BYTES + } + ], settings || fixtureSettings()); +} + +function fixtureExportOptions(settings) { return { modelBytes: FIXTURE_MODEL_BYTES, - textureBytes: FIXTURE_TEXTURE_BYTES + textureResolution: fixtureTextureResolution(settings) }; } @@ -41,5 +54,6 @@ module.exports = { FIXTURE_MODEL_BYTES, FIXTURE_TEXTURE_BYTES, fixtureSettings, + fixtureTextureResolution, fixtureExportOptions }; diff --git a/plugins/easy_model_entities/src/tests/mdTransform.js b/plugins/easy_model_entities/src/tests/mdTransform.js index 23917446..ad8b4f40 100644 --- a/plugins/easy_model_entities/src/tests/mdTransform.js +++ b/plugins/easy_model_entities/src/tests/mdTransform.js @@ -1,3 +1,22 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + module.exports = { process(sourceText) { return {code: `module.exports = ${JSON.stringify(sourceText)};`}; diff --git a/plugins/easy_model_entities/src/tests/modelDimensions.test.js b/plugins/easy_model_entities/src/tests/modelDimensions.test.js index 31188d32..d050b549 100644 --- a/plugins/easy_model_entities/src/tests/modelDimensions.test.js +++ b/plugins/easy_model_entities/src/tests/modelDimensions.test.js @@ -1,3 +1,22 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + const {ModelDimensions} = require('../model/ModelDimensions'); describe('deriveDimensions', () => { @@ -8,7 +27,6 @@ describe('deriveDimensions', () => { expect(dims.width).toBe(1); // 16px / 16 expect(dims.height).toBe(2); // 32px / 16 expect(dims.eyeHeight).toBe(1.8); // 2 * 0.9 (biped) - expect(dims.visibleBoundsOffset).toEqual([0.0, 1, 0.0]); }); test('eye height factor depends on body type', () => { @@ -32,46 +50,18 @@ describe('deriveDimensions', () => { {minX: 5, minY: 5, minZ: 5, maxX: 5, maxY: 5, maxZ: 5}, 'biped')).toEqual(ModelDimensions.FALLBACK); }); - - test('does not share the fallback offset array instance', () => { - const a = ModelDimensions.deriveDimensions(null, 'biped'); - a.visibleBoundsOffset[0] = 99; - expect(ModelDimensions.FALLBACK.visibleBoundsOffset[0]).toBe(0.0); - }); }); describe('applyModelDimensions', () => { - const dims = { - width: 1, - height: 2, - eyeHeight: 1.8, - visibleBoundsWidth: 1.1, - visibleBoundsHeight: 2.2, - visibleBoundsOffset: [0.0, 1, 0.0] - }; + const dims = {width: 1, height: 2, eyeHeight: 1.8}; function settings() { - return { - dimensions: {width: 0, height: 0, eyeHeight: 0}, - rendering: { - visibleBoundsWidth: 0, - visibleBoundsHeight: 0, - visibleBoundsOffset: [0, 0, 0] - } - }; + return {dimensions: {width: 0, height: 0, eyeHeight: 0}}; } - test('overlays dimensions and visible bounds', () => { + test('overlays dimensions', () => { const result = ModelDimensions.applyModelDimensions(settings(), dims); expect(result.dimensions).toEqual({width: 1, height: 2, eyeHeight: 1.8}); - expect(result.rendering.visibleBoundsWidth).toBe(1.1); - expect(result.rendering.visibleBoundsOffset).toEqual([0.0, 1, 0.0]); - }); - - test('does not share the offset array with the source', () => { - const result = ModelDimensions.applyModelDimensions(settings(), dims); - result.rendering.visibleBoundsOffset[0] = 99; - expect(dims.visibleBoundsOffset[0]).toBe(0.0); }); test('returns settings unchanged when no dimensions are given', () => { diff --git a/plugins/easy_model_entities/src/tests/presetDetector.test.js b/plugins/easy_model_entities/src/tests/presetDetector.test.js index 99713116..1c8dfa02 100644 --- a/plugins/easy_model_entities/src/tests/presetDetector.test.js +++ b/plugins/easy_model_entities/src/tests/presetDetector.test.js @@ -1,3 +1,22 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + const {PresetDetector} = require('../model/PresetDetector'); function stats(boneNames) { diff --git a/plugins/easy_model_entities/src/tests/readme.test.js b/plugins/easy_model_entities/src/tests/readme.test.js index ad6ecde8..f70fd3b7 100644 --- a/plugins/easy_model_entities/src/tests/readme.test.js +++ b/plugins/easy_model_entities/src/tests/readme.test.js @@ -1,3 +1,22 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + const {buildReadme} = require('../builders/readme'); const {fixtureSettings} = require('./fixtureData'); @@ -19,6 +38,18 @@ describe('buildReadme', () => { expect(readme).toContain('Data Pack'); }); + test('names the profile-id-prefixed pack files', () => { + expect(readme).toContain('lizard_datapack.zip'); + expect(readme).toContain('lizard_resourcepack.zip'); + }); + + test('links to the CurseForge and Modrinth distribution pages', () => { + expect(readme).toContain( + 'https://www.curseforge.com/minecraft/mc-mods/easy-model-entities'); + expect(readme).toContain( + 'https://modrinth.com/mod/easy-model-entities'); + }); + test('references the profile id', () => { expect(readme).toContain('example:lizard'); }); diff --git a/plugins/easy_model_entities/src/tests/resourceLocation.test.js b/plugins/easy_model_entities/src/tests/resourceLocation.test.js index 3bb7201e..dd784de1 100644 --- a/plugins/easy_model_entities/src/tests/resourceLocation.test.js +++ b/plugins/easy_model_entities/src/tests/resourceLocation.test.js @@ -1,3 +1,22 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + const {ResourceLocation} = require('../utils/ResourceLocation'); describe('resourceLocation', () => { diff --git a/plugins/easy_model_entities/src/tests/schemaConsistency.test.js b/plugins/easy_model_entities/src/tests/schemaConsistency.test.js index 0b6bae98..13c02b53 100644 --- a/plugins/easy_model_entities/src/tests/schemaConsistency.test.js +++ b/plugins/easy_model_entities/src/tests/schemaConsistency.test.js @@ -1,3 +1,22 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + const {applyTemplate, deepMerge, SELECTABLE_PRESET_TYPES} = require( '../model/templates'); const {buildServerProfile} = require('../builders/serverProfile'); @@ -96,7 +115,6 @@ describe('block entity profiles', () => { describe('customized settings emit only the deviating values', () => { const settings = deepMerge(withId(applyTemplate('quadruped_wandering')), { - version: 'demo-stone-turtle-v4', dimensions: {width: 1.2, height: 0.85, eyeHeight: 0.6}, movement: {speed: 0.06, stepHeight: 0.4, gravity: true}, attributes: {maxHealth: 12, movementSpeed: 0.06, followRange: 12} @@ -107,7 +125,6 @@ describe('customized settings emit only the deviating values', () => { schema_version: '0.1.0', model_type: 'entity', preset_type: 'quadruped_wandering', - version: 'demo-stone-turtle-v4', client: {render_profile: 'example:lizard'}, dimensions: {width: 1.2, height: 0.85}, movement: {speed: 0.06, step_height: 0.4}, diff --git a/plugins/easy_model_entities/src/tests/templates.test.js b/plugins/easy_model_entities/src/tests/templates.test.js index a8528160..363ff18c 100644 --- a/plugins/easy_model_entities/src/tests/templates.test.js +++ b/plugins/easy_model_entities/src/tests/templates.test.js @@ -1,3 +1,22 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + const { getDefaults, applyTemplate, @@ -84,10 +103,11 @@ describe('presetDefaults', () => { {mode: 'ambient', lookAtPlayers: true, randomStroll: true}); }); - test('deepMerge overrides nested values and copies arrays', () => { - const merged = deepMerge(presetDefaults('cuboid_still'), - {rendering: {scale: 2}}); - expect(merged.rendering.scale).toBe(2); - expect(merged.rendering.visibleBoundsOffset).toEqual([0, 0.5, 0]); - }); + test('deepMerge overrides nested values and preserves untouched defaults', + () => { + const merged = deepMerge(presetDefaults('cuboid_still'), + {rendering: {scale: 2}}); + expect(merged.rendering.scale).toBe(2); + expect(merged.rendering.shadowRadius).toBe(0.5); + }); }); diff --git a/plugins/easy_model_entities/src/tests/textureResolver.test.js b/plugins/easy_model_entities/src/tests/textureResolver.test.js new file mode 100644 index 00000000..ab23f9b9 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/textureResolver.test.js @@ -0,0 +1,85 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const { + parseExternalLocation, + resolveTextures +} = require('../utils/TextureResolver'); + +const SETTINGS = {namespace: 'example', profileId: 'lizard'}; +const BYTES = Uint8Array.from([1, 2, 3]); + +describe('parseExternalLocation', () => { + test('derives from an assets path', () => { + expect(parseExternalLocation({ + path: 'C:/mc/assets/minecraft/textures/entity/chest/normal.png' + })).toBe('minecraft:textures/entity/chest/normal.png'); + }); + + test('derives from namespace + folder + name', () => { + expect(parseExternalLocation({ + namespace: 'minecraft', folder: 'entity/chest', name: 'normal.png' + })).toBe('minecraft:textures/entity/chest/normal.png'); + }); + + test('treats a foreign mod namespace as external', () => { + expect(parseExternalLocation({ + namespace: 'othermod', folder: 'entity', name: 'thing' + })).toBe('othermod:textures/entity/thing.png'); + }); + + test('returns null for an empty namespace and no assets path', () => { + expect(parseExternalLocation({ + namespace: '', folder: 'block', name: 'eye_texture', path: '' + })).toBeNull(); + }); +}); + +describe('resolveTextures', () => { + test('single custom texture maps to the default path and is packed', () => { + const result = resolveTextures( + [{ + index: 0, namespace: '', folder: '', name: '', path: '', + bytes: BYTES + }], + SETTINGS); + expect(result.texture).toBeNull(); + expect(result.textures).toEqual({}); + expect(result.packed).toEqual([{fileName: 'lizard.png', bytes: BYTES}]); + }); + + test('mixes a vanilla index 0 with a custom index 1', () => { + const result = resolveTextures([ + { + index: 0, namespace: 'minecraft', folder: 'entity/chest', + name: 'normal.png', path: '', bytes: BYTES + }, + { + index: 1, namespace: '', folder: 'block', name: 'eye_texture', + path: '', bytes: BYTES + } + ], SETTINGS); + + expect(result.texture).toBe('minecraft:textures/entity/chest/normal.png'); + expect(result.textures).toEqual( + {1: 'example:textures/entity/lizard_1.png'}); + expect(result.packed).toEqual( + [{fileName: 'lizard_1.png', bytes: BYTES}]); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/validation.test.js b/plugins/easy_model_entities/src/tests/validation.test.js index f24b99d3..91436662 100644 --- a/plugins/easy_model_entities/src/tests/validation.test.js +++ b/plugins/easy_model_entities/src/tests/validation.test.js @@ -1,3 +1,22 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + const {Validator} = require('../model/Validator'); const {fixtureSettings} = require('./fixtureData'); @@ -62,15 +81,14 @@ describe('validateSettings', () => { expect.arrayContaining(['MISSING_MODEL', 'MISSING_TEXTURE'])); }); - test('warns when multiple textures are present', () => { + test('allows multiple textures without warning', () => { const result = Validator.validateSettings(fixtureSettings(), { hasModel: true, hasTexture: true, - textureCount: 3, boneNames: QUADRUPED_BONES }); expect(result.valid).toBe(true); - expect(codes(result.warnings)).toContain('MULTIPLE_TEXTURES'); + expect(codes(result.warnings)).not.toContain('MULTIPLE_TEXTURES'); }); test('warns about performance budgets without blocking', () => { diff --git a/plugins/easy_model_entities/src/tests/versionMatrix.test.js b/plugins/easy_model_entities/src/tests/versionMatrix.test.js index bc33b57c..78b04501 100644 --- a/plugins/easy_model_entities/src/tests/versionMatrix.test.js +++ b/plugins/easy_model_entities/src/tests/versionMatrix.test.js @@ -1,3 +1,22 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + const { getVersions, getEnabledVersions, diff --git a/plugins/easy_model_entities/src/ui/exportDialog.js b/plugins/easy_model_entities/src/ui/exportDialog.js index 16de64cd..ec00f3db 100644 --- a/plugins/easy_model_entities/src/ui/exportDialog.js +++ b/plugins/easy_model_entities/src/ui/exportDialog.js @@ -58,6 +58,7 @@ function presetOptions(modelType, experimental, ensure) { ids.forEach((id) => { options[id] = presetLabel(id); }); + return options; } @@ -124,6 +125,7 @@ function versionOptions() { options[version.id] = version.enabled ? version.label : `${version.label} (coming soon)`; }); + return options; } @@ -131,7 +133,6 @@ function settingsToForm(settings) { return { namespace: settings.namespace, profileId: settings.profileId, - version: settings.version || '', targetVersion: settings.targetVersion, hostEntityType: settings.host.entityType, movementType: settings.host.movementType, @@ -148,9 +149,6 @@ function settingsToForm(settings) { followRange: settings.attributes.followRange, scale: settings.rendering.scale, shadowRadius: settings.rendering.shadowRadius, - visibleBoundsWidth: settings.rendering.visibleBoundsWidth, - visibleBoundsHeight: settings.rendering.visibleBoundsHeight, - visibleBoundsOffset: settings.rendering.visibleBoundsOffset.slice(), animationMode: settings.animation.mode, swingSpeed: settings.animation.swingSpeed, walkSpeedMultiplier: settings.animation.walkSpeedMultiplier @@ -162,7 +160,6 @@ function formToSettings(form, base) { schemaVersion: base.schemaVersion, modelType: base.modelType, presetType: base.presetType, - version: base.version || '', namespace: String(form.namespace || '').trim(), profileId: String(form.profileId || '').trim(), targetVersion: base.targetVersion, @@ -193,10 +190,7 @@ function formToSettings(form, base) { }, rendering: { scale: Number(form.scale), - shadowRadius: Number(form.shadowRadius), - visibleBoundsWidth: Number(form.visibleBoundsWidth), - visibleBoundsHeight: Number(form.visibleBoundsHeight), - visibleBoundsOffset: (form.visibleBoundsOffset || [0, 0, 0]).map(Number) + shadowRadius: Number(form.shadowRadius) }, animation: { mode: form.animationMode, @@ -220,25 +214,22 @@ function activePreset(form) { } function presetFormValues(presetType, modelType, modelDimensions) { - const settings = ModelDimensions.applyModelDimensions( - applyTemplate(presetType, modelType), modelDimensions); - const form = settingsToForm(settings); + const form = settingsToForm(ModelDimensions.applyModelDimensions( + applyTemplate(presetType, modelType), modelDimensions)); delete form.namespace; delete form.profileId; delete form.targetVersion; - delete form.version; + return form; } function resolveExportSettings(form, base, modelDimensions) { const exportType = form.exportType || 'packs'; - const modelOnly = exportType === 'model_only'; const modelType = activeModelType(form); - const blockEntity = modelType === MODEL_TYPE_BLOCK_ENTITY; const preset = activePreset(form); let settings; - if (!blockEntity && preset === 'custom') { + if (modelType !== MODEL_TYPE_BLOCK_ENTITY && preset === 'custom') { settings = deepMerge(getDefaults(), base); } else { settings = ModelDimensions.applyModelDimensions( @@ -250,7 +241,6 @@ function resolveExportSettings(form, base, modelDimensions) { settings.presetType = preset; settings.namespace = String(form.namespace || '').trim(); settings.profileId = String(form.profileId || '').trim(); - settings.version = String(form.version || '').trim(); settings.targetVersion = form.targetVersion; if (form.customize) { @@ -260,7 +250,8 @@ function resolveExportSettings(form, base, modelDimensions) { settings.customize = !!form.customize; settings.exportType = exportType; settings.exportTarget = exportType === 'packs' ? 'packs' : 'mod_project'; - settings.modelOnly = modelOnly; + settings.modelOnly = exportType === 'model_only'; + return settings; } @@ -326,12 +317,6 @@ function buildFormConfig(settings, ui) { label: t('eme.field.profileId'), type: 'text', value: values.profileId }, - version: { - label: t('eme.field.version'), - type: 'text', - value: values.version, - placeholder: 'v1' - }, targetVersion: { label: t('eme.field.targetVersion'), type: 'select', @@ -448,21 +433,6 @@ function buildFormConfig(settings, ui) { label: t('eme.field.shadowRadius'), type: 'number', value: values.shadowRadius, step: 0.1 }, showRender), - visibleBoundsWidth: advancedField( - { - label: t('eme.field.visibleBoundsWidth'), type: 'number', - value: values.visibleBoundsWidth, step: 0.1 - }, showRender), - visibleBoundsHeight: advancedField( - { - label: t('eme.field.visibleBoundsHeight'), type: 'number', - value: values.visibleBoundsHeight, step: 0.1 - }, showRender), - visibleBoundsOffset: advancedField( - { - label: t('eme.field.visibleBoundsOffset'), type: 'vector', - value: values.visibleBoundsOffset - }, showRender), animation_header: advancedField( {type: 'info', text: `### ${t('eme.section.animation')}`}, @@ -514,9 +484,7 @@ function openExportDialog(options) { const key = `${modelType}|${preset}`; if (key !== lastKey) { lastKey = key; - const isCustomEntity = modelType === MODEL_TYPE_ENTITY - && preset === 'custom'; - if (!isCustomEntity) { + if (modelType !== MODEL_TYPE_ENTITY || preset !== 'custom') { dialog.setFormValues( presetFormValues(preset, modelType, modelDimensions), false); } @@ -531,6 +499,7 @@ function openExportDialog(options) { } }); dialog.show(); + return dialog; } diff --git a/plugins/easy_model_entities/src/utils/TextureResolver.js b/plugins/easy_model_entities/src/utils/TextureResolver.js new file mode 100644 index 00000000..f5fd3a48 --- /dev/null +++ b/plugins/easy_model_entities/src/utils/TextureResolver.js @@ -0,0 +1,104 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Resolves Blockbench textures to mod resource locations. A texture that lives +// under an existing assets//textures tree (vanilla or another mod) is only +// referenced; everything else is a custom texture packed as a PNG file. + +const ASSETS_PATTERN = /assets\/([a-z0-9_.-]+)\/textures\/(.+?)(?:\.png)?$/i; + +function stripPng(value) { + return value.replace(/\.png$/i, ''); +} + +function trimSlashes(value) { + return value.replace(/^\/+|\/+$/g, ''); +} + +function defaultTextureLocation(settings) { + return `${settings.namespace}:textures/entity/${settings.profileId}.png`; +} + +function customFileName(settings, index) { + return index === 0 + ? `${settings.profileId}.png` + : `${settings.profileId}_${index}.png`; +} + +function customLocation(settings, index) { + return `${settings.namespace}:textures/entity/${customFileName(settings, + index)}`; +} + +function parseExternalLocation(descriptor) { + const path = String(descriptor.path || '').replace(/\\/g, '/'); + const match = path.match(ASSETS_PATTERN); + if (match) { + return `${match[1].toLowerCase()}:textures/${match[2].toLowerCase()}.png`; + } + + const namespace = String(descriptor.namespace || '').trim().toLowerCase(); + if (!namespace) { + return null; + } + + const folder = trimSlashes(String(descriptor.folder || '').trim()); + const name = stripPng(String(descriptor.name || '').trim()).toLowerCase(); + if (!name) { + return null; + } + + const rest = folder ? `${folder.toLowerCase()}/${name}` : name; + + return `${namespace}:textures/${rest}.png`; +} + +function resolveTextures(descriptors, settings) { + const defaultLocation = defaultTextureLocation(settings); + const textures = {}; + const packed = []; + let texture = null; + + (descriptors || []).forEach((descriptor, position) => { + const index = Number.isInteger(descriptor.index) ? descriptor.index + : position; + let location = parseExternalLocation(descriptor); + if (!location) { + location = customLocation(settings, index); + packed.push({ + fileName: customFileName(settings, index), + bytes: descriptor.bytes + }); + } + + // Index 0 follows the mod's default path unless it deviates (e.g. vanilla); + // every other index is always spelled out in the textures map. + if (index === 0) { + if (location !== defaultLocation) { + texture = location; + } + } else { + textures[index] = location; + } + }); + + return {texture, textures, packed}; +} + +module.exports = {resolveTextures, parseExternalLocation}; diff --git a/plugins/easy_model_entities/src/utils/hash.js b/plugins/easy_model_entities/src/utils/hash.js new file mode 100644 index 00000000..d41a9945 --- /dev/null +++ b/plugins/easy_model_entities/src/utils/hash.js @@ -0,0 +1,33 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +// Deterministic, dependency-free 32-bit FNV-1a hash rendered as 8 hex chars. +// Used to derive a stable content version stamp without requiring node crypto. +function hashString(value) { + let hash = 0x811c9dc5; + const text = String(value); + for (let i = 0; i < text.length; i++) { + hash ^= text.codePointAt(i); + hash = Math.imul(hash, 0x01000193); + } + + return (hash >>> 0).toString(16).padStart(8, '0'); +} + +module.exports = {hashString}; From 8bfd85931be9efbfe5af8f140d495d3471444a09 Mon Sep 17 00:00:00 2001 From: Markus Bordihn Date: Tue, 16 Jun 2026 20:06:30 +0200 Subject: [PATCH 3/8] Fixed plugin name to make sure it's not longer an exporter only. --- plugins.json | 2 +- plugins/easy_model_entities/about.md | 2 +- plugins/easy_model_entities/easy_model_entities.js | 2 +- plugins/easy_model_entities/src/index.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins.json b/plugins.json index d5c92362..efaaf73f 100644 --- a/plugins.json +++ b/plugins.json @@ -1536,7 +1536,7 @@ "has_changelog": true }, "easy_model_entities": { - "title": "Easy Model Entities Exporter", + "title": "Easy Model Entities", "author": "Markus Bordihn", "icon": "icon.png", "description": "Export Blockbench models for the Easy Model Entities mod (Minecraft: Java Edition) as ready-to-install Data Pack and Resource Pack files.", diff --git a/plugins/easy_model_entities/about.md b/plugins/easy_model_entities/about.md index 25fc7c24..e73e430f 100644 --- a/plugins/easy_model_entities/about.md +++ b/plugins/easy_model_entities/about.md @@ -1,4 +1,4 @@ -# Easy Model Entities Exporter +# Easy Model Entities > **Beta:** This is an early release to gather feedback. Some settings, file formats and the export > output may still change before the final version. Please report problems and ideas on the diff --git a/plugins/easy_model_entities/easy_model_entities.js b/plugins/easy_model_entities/easy_model_entities.js index ad6e8b57..ab24a5ea 100644 --- a/plugins/easy_model_entities/easy_model_entities.js +++ b/plugins/easy_model_entities/easy_model_entities.js @@ -3227,7 +3227,7 @@ function openDialog() { } BBPlugin.register('easy_model_entities', { - title: 'Easy Model Entities Exporter', + title: 'Easy Model Entities', icon: 'icon.png', author: 'Markus Bordihn', description: 'Export Blockbench models for the Easy Model Entities mod (Minecraft: Java Edition) as ready-to-install Data Pack and Resource Pack files.', diff --git a/plugins/easy_model_entities/src/index.js b/plugins/easy_model_entities/src/index.js index b14ddc80..a4fe2e59 100644 --- a/plugins/easy_model_entities/src/index.js +++ b/plugins/easy_model_entities/src/index.js @@ -255,7 +255,7 @@ function openDialog() { } BBPlugin.register('easy_model_entities', { - title: 'Easy Model Entities Exporter', + title: 'Easy Model Entities', icon: 'icon.png', author: 'Markus Bordihn', description: 'Export Blockbench models for the Easy Model Entities mod (Minecraft: Java Edition) as ready-to-install Data Pack and Resource Pack files.', From badeecae78056ac307c56dde45448bcd83f5a669 Mon Sep 17 00:00:00 2001 From: Markus Bordihn Date: Wed, 24 Jun 2026 20:56:17 +0200 Subject: [PATCH 4/8] Added support for new body types and movement types in the export dialog options. Added `VisibleBounds` class to derive and manage rendering bounds based on model geometry. Added tests for contract synchronization, mod updates, and pack metadata to ensure compliance with the mod API. Improved texture handling in `TextureResolver.js` to distinguish between custom and referenced textures (over namespace). Improved own "Easy Model Entity" format for better texture handling and support. Refactored codebase for better readability. --- .../easy_model_entities.js | 1081 +++-- plugins/easy_model_entities/icon.png | Bin 5255 -> 2597 bytes .../src/BlockbenchAdapter.js | 18 +- .../src/builders/packMeta.js | 26 +- .../src/builders/renderProfile.js | 31 +- .../src/builders/serverProfile.js | 3 +- .../src/format/EmeCodecHooks.js | 3 +- .../src/format/EmeFormat.js | 1 + .../src/i18n/translations.js | 34 + plugins/easy_model_entities/src/index.js | 14 +- .../src/model/Validator.js | 106 +- .../src/model/VisibleBounds.js | 109 + .../src/model/presetTypes.js | 170 +- .../src/model/templates.js | 41 +- .../src/model/versionMatrix.js | 28 +- .../easy_model_entities/src/package-lock.json | 4032 ++++++++--------- plugins/easy_model_entities/src/package.json | 10 +- .../src/resources/icon.png | Bin 5255 -> 2597 bytes .../src/tests/contractSync.test.js | 118 + .../src/tests/exportDialog.test.js | 29 +- .../tests/fixtures/easy-model-api-0.1.0.json | 118 + .../src/tests/modUpdate.test.js | 145 + .../src/tests/packMeta.test.js | 62 + .../src/tests/templates.test.js | 8 +- .../src/tests/textureResolver.test.js | 62 +- .../src/tests/validation.test.js | 26 +- .../src/tests/versionMatrix.test.js | 27 +- .../src/ui/exportDialog.js | 202 +- .../src/ui/exportDialogOptions.js | 155 + .../src/ui/texturePanel.js | 72 + .../src/utils/TextureResolver.js | 45 +- 31 files changed, 4127 insertions(+), 2649 deletions(-) create mode 100644 plugins/easy_model_entities/src/model/VisibleBounds.js create mode 100644 plugins/easy_model_entities/src/tests/contractSync.test.js create mode 100644 plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.1.0.json create mode 100644 plugins/easy_model_entities/src/tests/modUpdate.test.js create mode 100644 plugins/easy_model_entities/src/tests/packMeta.test.js create mode 100644 plugins/easy_model_entities/src/ui/exportDialogOptions.js create mode 100644 plugins/easy_model_entities/src/ui/texturePanel.js diff --git a/plugins/easy_model_entities/easy_model_entities.js b/plugins/easy_model_entities/easy_model_entities.js index ab24a5ea..9a315226 100644 --- a/plugins/easy_model_entities/easy_model_entities.js +++ b/plugins/easy_model_entities/easy_model_entities.js @@ -53,9 +53,6 @@ class BlockbenchAdapter { return Number.isInteger(id) && id >= 0 ? id : position; } - // All project textures as plain descriptors for TextureResolver. External - // (vanilla / other-mod) textures are referenced by location; custom textures - // carry their PNG bytes to be packed. static collectTextures() { if (typeof Texture === 'undefined' || !Texture.all) { return []; @@ -74,6 +71,8 @@ class BlockbenchAdapter { static getModelStats() { const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; const groups = typeof Group !== 'undefined' && Group.all ? Group.all : []; + const animations = + typeof Animation !== 'undefined' && Animation.all ? Animation.all : []; const texture = BlockbenchAdapter.#firstTexture(); let maxDepth = 0; @@ -94,6 +93,7 @@ class BlockbenchAdapter { hasTexture: !!texture, cubeCount: cubes.length, boneCount: groups.length, + animationCount: animations.length, hierarchyDepth: maxDepth, boneNames: groups.map((group) => group.name), textureWidth: texture ? texture.width : undefined, @@ -133,6 +133,15 @@ class BlockbenchAdapter { return bounds; } + static getModelCubes() { + const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; + + return cubes.map((cube) => ({ + from: (cube.from || [0, 0, 0]).slice(), + to: (cube.to || [0, 0, 0]).slice() + })); + } + static getProjectName() { if (typeof Project === 'undefined' || !Project) { return ''; @@ -184,9 +193,6 @@ class BlockbenchAdapter { return zip.generateAsync({type: 'uint8array'}); } - // Wraps a ready-to-drop datapack.zip and resourcepack.zip plus the README - // into a single outer ZIP, so the user can move the inner ZIPs straight into - // their datapacks/ and resourcepacks/ folders without unpacking. static exportPackBundle(bundle, name) { return Promise.all([ BlockbenchAdapter.#zipToUint8(bundle.datapack), @@ -447,27 +453,35 @@ module.exports = { const {getPackFormats} = __webpack_require__(954); -function buildMcmeta(settings, packFormat, description) { +function buildPack(descriptor, description) { + const pack = {pack_format: descriptor.packFormat}; + if (descriptor.minFormat) { + pack.min_format = descriptor.minFormat; + } + if (descriptor.maxFormat) { + pack.max_format = descriptor.maxFormat; + } + pack.description = description; + + return {pack}; +} + +function buildMcmeta(settings, kind, description) { const formats = getPackFormats(settings.targetVersion); if (!formats) { throw new Error( `Unsupported or disabled target version: ${settings.targetVersion}`); } - return { - pack: { - pack_format: formats[packFormat], - description: description - } - }; + return buildPack(formats[kind], description); } function buildDataPackMcmeta(settings) { - return buildMcmeta(settings, 'dataFormat', + return buildMcmeta(settings, 'data', 'Easy Model Entities server profiles'); } function buildResourcePackMcmeta(settings) { - return buildMcmeta(settings, 'resourceFormat', + return buildMcmeta(settings, 'resource', 'Easy Model Entities render assets'); } @@ -617,20 +631,25 @@ const { } = __webpack_require__(151); const {diffFlat, assignIfPresent} = __webpack_require__(640); -// Block entities render as a static cuboid block, so they always use the -// "static" render preset regardless of their server-side block entity preset. function renderPresetType(settings) { return settings.modelType === MODEL_TYPE_BLOCK_ENTITY ? 'static' : settings.presetType; } function buildRendering(settings) { + const rendering = settings.rendering; return diffFlat({ - scale: settings.rendering.scale, - shadow_radius: settings.rendering.shadowRadius + scale: rendering.scale, + shadow_radius: rendering.shadowRadius, + visible_bounds_width: rendering.visibleBoundsWidth ?? 0, + visible_bounds_height: rendering.visibleBoundsHeight ?? 0, + visible_bounds_offset: rendering.visibleBoundsOffset || [0, 0, 0] }, { scale: 1, - shadow_radius: presetShadowRadius(renderPresetType(settings)) + shadow_radius: presetShadowRadius(renderPresetType(settings)), + visible_bounds_width: 0, + visible_bounds_height: 0, + visible_bounds_offset: [0, 0, 0] }); } @@ -639,16 +658,23 @@ function buildAnimation(settings) { const animation = diffFlat({ mode: settings.animation.mode, swing_speed: settings.animation.swingSpeed, - walk_speed_multiplier: settings.animation.walkSpeedMultiplier + walk_speed_multiplier: settings.animation.walkSpeedMultiplier, + idle_strength: settings.animation.idleStrength ?? 1, + gait: settings.animation.gait || 'natural' }, { mode: defaultMode, swing_speed: 1, - walk_speed_multiplier: 1 + walk_speed_multiplier: 1, + idle_strength: 1, + gait: 'natural' }); + // Animation timing is meaningless when animation is disabled. if (settings.animation.mode === 'none') { delete animation.swing_speed; delete animation.walk_speed_multiplier; + delete animation.idle_strength; + delete animation.gait; } return animation; @@ -667,11 +693,6 @@ function buildRenderProfile(settings, textureResolution) { profile.body_type = settings.host.bodyType; } - // The model and the index-0 texture follow the mod's conventional default - // path (namespace:easy_model_entities/models/id and - // namespace:textures/entity/id.png), so they are omitted whenever they match - // the default. Only deviating texture locations (e.g. vanilla index 0 or any - // index > 0) are spelled out for the mod's ModelTextureResolver. if (textureResolution && textureResolution.texture) { profile.texture = textureResolution.texture; } @@ -718,6 +739,7 @@ const { presetDimensions, movementDefaults, behaviorModeFor, + wandersByMovement, isCustom, MODEL_TYPE_BLOCK_ENTITY, MODEL_TYPE_ENTITY @@ -742,7 +764,7 @@ function effectiveDefaults(settings) { behavior: { mode: mode, look_at_players: mode === 'idle_only' || mode === 'ambient', - random_stroll: movementType === 'ground' && mode === 'ambient' + random_stroll: wandersByMovement(movementType) && mode === 'ambient' }, attributes: { max_health: DEFAULT_MAX_HEALTH, @@ -866,6 +888,7 @@ module.exports = {buildServerProfile}; */ const {BlockbenchAdapter} = __webpack_require__(833); +const {pickModelSettings} = __webpack_require__(668); const {FORMAT_ID} = __webpack_require__(995); const EME_SETTINGS_KEY = 'emeSettings'; @@ -877,7 +900,7 @@ function handleCompile(event) { const settings = BlockbenchAdapter.loadSettings(); if (settings) { - event.model[EME_SETTINGS_KEY] = settings; + event.model[EME_SETTINGS_KEY] = pickModelSettings(settings); } } @@ -956,6 +979,7 @@ function registerEmeFormat(icon) { box_uv: true, optional_box_uv: true, single_texture: false, + texture_folder: true, bone_rig: true, centered_grid: true, animated_textures: false, @@ -1037,9 +1061,16 @@ const EN = { 'eme.field.followRange': 'Follow Range', 'eme.field.scale': 'Scale', 'eme.field.shadowRadius': 'Shadow Radius', + 'eme.field.visibleBoundsWidth': 'Visible Bounds Width (0 = auto)', + 'eme.field.visibleBoundsHeight': 'Visible Bounds Height (0 = auto)', + 'eme.field.visibleBoundsOffsetX': 'Visible Bounds Offset X', + 'eme.field.visibleBoundsOffsetY': 'Visible Bounds Offset Y', + 'eme.field.visibleBoundsOffsetZ': 'Visible Bounds Offset Z', 'eme.field.animationMode': 'Animation Mode', 'eme.field.swingSpeed': 'Swing Speed', 'eme.field.walkSpeedMultiplier': 'Walk Speed Multiplier', + 'eme.field.idleStrength': 'Idle Strength', + 'eme.field.gait': 'Gait', 'eme.section.host': 'Host', 'eme.section.dimensions': 'Dimensions', 'eme.section.movement': 'Movement', @@ -1061,6 +1092,8 @@ const EN = { 'eme.preset.quadruped_wandering': 'Quadruped (4 legs, ground, wandering)', 'eme.preset.aquatic_still': 'Aquatic (fish, still)', 'eme.preset.aquatic_swimming': 'Aquatic (fish, swimming)', + 'eme.preset.amphibious_still': 'Amphibious (land/water, still)', + 'eme.preset.amphibious_wandering': 'Amphibious (land/water, wandering)', 'eme.preset.winged_still': 'Winged (bird, perched)', 'eme.preset.winged_wandering': 'Winged (bird, flying)', 'eme.preset.winged_humanoid_still': 'Winged humanoid (standing)', @@ -1074,13 +1107,19 @@ const EN = { 'eme.body.biped': 'Biped (2 legs)', 'eme.body.quadruped': 'Quadruped (4 legs)', 'eme.body.aquatic': 'Aquatic', + 'eme.body.amphibious': 'Amphibious', 'eme.body.winged': 'Winged', 'eme.body.winged_humanoid': 'Winged humanoid', 'eme.body.arthropod': 'Arthropod', 'eme.body.cuboid': 'Cuboid', 'eme.body.floating': 'Floating', 'eme.movement.ground': 'Ground', + 'eme.movement.water': 'Water', + 'eme.movement.amphibious': 'Amphibious', 'eme.movement.static': 'Static', + 'eme.gait.natural': 'Natural', + 'eme.gait.feline': 'Feline (light, quick)', + 'eme.gait.ungulate': 'Ungulate (heavy, slow)', 'eme.behavior.idle_only': 'Idle only', 'eme.behavior.ambient': 'Ambient (wanders)', 'eme.behavior.static': 'Static', @@ -1090,6 +1129,8 @@ const EN = { 'eme.animation.none': 'None', 'eme.entity.ground_entity': 'Ground entity', 'eme.entity.static_entity': 'Static entity', + 'eme.entity.aquatic_entity': 'Aquatic entity', + 'eme.entity.amphibious_entity': 'Amphibious entity', 'eme.modelType.entity': 'Entity', 'eme.modelType.block_entity': 'Block Entity', 'eme.exportType.packs': 'Standalone: Data Pack + Resource Pack (ZIP)', @@ -1121,9 +1162,16 @@ const DE = { 'eme.field.followRange': 'Folgereichweite', 'eme.field.scale': 'Skalierung', 'eme.field.shadowRadius': 'Schattenradius', + 'eme.field.visibleBoundsWidth': 'Sichtbarkeitsbox-Breite (0 = auto)', + 'eme.field.visibleBoundsHeight': 'Sichtbarkeitsbox-Höhe (0 = auto)', + 'eme.field.visibleBoundsOffsetX': 'Sichtbarkeitsbox-Versatz X', + 'eme.field.visibleBoundsOffsetY': 'Sichtbarkeitsbox-Versatz Y', + 'eme.field.visibleBoundsOffsetZ': 'Sichtbarkeitsbox-Versatz Z', 'eme.field.animationMode': 'Animationsmodus', 'eme.field.swingSpeed': 'Schwunggeschwindigkeit', 'eme.field.walkSpeedMultiplier': 'Geh-Geschwindigkeitsfaktor', + 'eme.field.idleStrength': 'Leerlauf-Intensität', + 'eme.field.gait': 'Gangart', 'eme.section.host': 'Host', 'eme.section.dimensions': 'Abmessungen', 'eme.section.movement': 'Bewegung', @@ -1145,6 +1193,8 @@ const DE = { 'eme.preset.quadruped_wandering': 'Vierbeiner (4 Beine, Boden, umherlaufend)', 'eme.preset.aquatic_still': 'Wassertier (Fisch, ruhend)', 'eme.preset.aquatic_swimming': 'Wassertier (Fisch, schwimmend)', + 'eme.preset.amphibious_still': 'Amphibie (Land/Wasser, ruhend)', + 'eme.preset.amphibious_wandering': 'Amphibie (Land/Wasser, umherlaufend)', 'eme.preset.winged_still': 'Geflügelt (Vogel, sitzend)', 'eme.preset.winged_wandering': 'Geflügelt (Vogel, fliegend)', 'eme.preset.winged_humanoid_still': 'Geflügelter Humanoid (stehend)', @@ -1158,13 +1208,19 @@ const DE = { 'eme.body.biped': 'Zweibeiner (2 Beine)', 'eme.body.quadruped': 'Vierbeiner (4 Beine)', 'eme.body.aquatic': 'Wassertier', + 'eme.body.amphibious': 'Amphibie', 'eme.body.winged': 'Geflügelt', 'eme.body.winged_humanoid': 'Geflügelter Humanoid', 'eme.body.arthropod': 'Gliederfüßer', 'eme.body.cuboid': 'Quaderförmig', 'eme.body.floating': 'Schwebend', 'eme.movement.ground': 'Boden', + 'eme.movement.water': 'Wasser', + 'eme.movement.amphibious': 'Amphibisch', 'eme.movement.static': 'Statisch', + 'eme.gait.natural': 'Natürlich', + 'eme.gait.feline': 'Katzenartig (leicht, schnell)', + 'eme.gait.ungulate': 'Huftier (schwer, langsam)', 'eme.behavior.idle_only': 'Nur Leerlauf', 'eme.behavior.ambient': 'Umgebung (läuft umher)', 'eme.behavior.static': 'Statisch', @@ -1174,6 +1230,8 @@ const DE = { 'eme.animation.none': 'Keine', 'eme.entity.ground_entity': 'Boden-Entität', 'eme.entity.static_entity': 'Statische Entität', + 'eme.entity.aquatic_entity': 'Wasser-Entität', + 'eme.entity.amphibious_entity': 'Amphibien-Entität', 'eme.modelType.entity': 'Entität', 'eme.modelType.block_entity': 'Block-Entität', 'eme.exportType.packs': 'Standalone: Data Pack + Resource Pack (ZIP)', @@ -1445,10 +1503,16 @@ const { class Validator { static BUDGETS = { maxTextureSize: 2048, - maxModelFileSize: 2 * 1024 * 1024, + softTextureSize: 128, + maxModelFileSizeBytes: 2 * 1024 * 1024, + softModelFileSizeBytes: 1024 * 1024, maxCubeCount: 512, + softCubeCount: 384, maxBoneCount: 128, - maxHierarchyDepth: 32 + softBoneCount: 96, + maxHierarchyDepth: 32, + softHierarchyDepth: 24, + maxAnimationCount: 16 }; static REQUIRED_BODY_PARTS = { @@ -1475,6 +1539,18 @@ class Validator { return true; } + static #warnIfOverBudget(warnings, value, soft, max, softWarning, + maxWarning) { + if (!Validator.#isFiniteNumber(value)) { + return; + } + if (value > max) { + warnings.push(maxWarning); + } else if (value > soft) { + warnings.push(softWarning); + } + } + static validateSettings(settings, context) { const errors = []; const warnings = []; @@ -1571,38 +1647,62 @@ class Validator { if (Validator.#isFiniteNumber(ctx.textureWidth) && Validator.#isFiniteNumber(ctx.textureHeight)) { - if (ctx.textureWidth > Validator.BUDGETS.maxTextureSize - || ctx.textureHeight > Validator.BUDGETS.maxTextureSize) { - warnings.push({ - code: 'LARGE_TEXTURE', - message: `Texture larger than ${Validator.BUDGETS.maxTextureSize}x${Validator.BUDGETS.maxTextureSize}` + const textureSize = Math.max(ctx.textureWidth, ctx.textureHeight); + Validator.#warnIfOverBudget(warnings, textureSize, + Validator.BUDGETS.softTextureSize, Validator.BUDGETS.maxTextureSize, + { + code: 'SOFT_TEXTURE_SIZE', + message: `Texture larger than ${Validator.BUDGETS.softTextureSize}x${Validator.BUDGETS.softTextureSize}` + }, + { + code: 'LARGE_TEXTURE', + message: `Texture larger than ${Validator.BUDGETS.maxTextureSize}x${Validator.BUDGETS.maxTextureSize}` + }); + } + Validator.#warnIfOverBudget(warnings, ctx.modelFileSize, + Validator.BUDGETS.softModelFileSizeBytes, + Validator.BUDGETS.maxModelFileSizeBytes, + { + code: 'SOFT_MODEL_SIZE', + message: 'Model file larger than 1 MB' + }, + {code: 'LARGE_MODEL', message: 'Model file larger than 2 MB'}); + Validator.#warnIfOverBudget(warnings, ctx.cubeCount, + Validator.BUDGETS.softCubeCount, Validator.BUDGETS.maxCubeCount, + { + code: 'SOFT_CUBE_COUNT', + message: `More than ${Validator.BUDGETS.softCubeCount} cubes` + }, + { + code: 'HIGH_CUBE_COUNT', + message: `More than ${Validator.BUDGETS.maxCubeCount} cubes` }); - } - } - if (Validator.#isFiniteNumber(ctx.modelFileSize) && ctx.modelFileSize - > Validator.BUDGETS.maxModelFileSize) { - warnings.push( - {code: 'LARGE_MODEL', message: 'Model file larger than 2 MB'}); - } - if (Validator.#isFiniteNumber(ctx.cubeCount) && ctx.cubeCount - > Validator.BUDGETS.maxCubeCount) { - warnings.push({ - code: 'HIGH_CUBE_COUNT', - message: `More than ${Validator.BUDGETS.maxCubeCount} cubes` - }); - } - if (Validator.#isFiniteNumber(ctx.boneCount) && ctx.boneCount - > Validator.BUDGETS.maxBoneCount) { - warnings.push({ - code: 'HIGH_BONE_COUNT', - message: `More than ${Validator.BUDGETS.maxBoneCount} bones` - }); - } - if (Validator.#isFiniteNumber(ctx.hierarchyDepth) && ctx.hierarchyDepth - > Validator.BUDGETS.maxHierarchyDepth) { + Validator.#warnIfOverBudget(warnings, ctx.boneCount, + Validator.BUDGETS.softBoneCount, Validator.BUDGETS.maxBoneCount, + { + code: 'SOFT_BONE_COUNT', + message: `More than ${Validator.BUDGETS.softBoneCount} bones` + }, + { + code: 'HIGH_BONE_COUNT', + message: `More than ${Validator.BUDGETS.maxBoneCount} bones` + }); + Validator.#warnIfOverBudget(warnings, ctx.hierarchyDepth, + Validator.BUDGETS.softHierarchyDepth, + Validator.BUDGETS.maxHierarchyDepth, + { + code: 'SOFT_HIERARCHY_DEPTH', + message: `Hierarchy deeper than ${Validator.BUDGETS.softHierarchyDepth}` + }, + { + code: 'DEEP_HIERARCHY', + message: `Hierarchy deeper than ${Validator.BUDGETS.maxHierarchyDepth}` + }); + if (Validator.#isFiniteNumber(ctx.animationCount) + && ctx.animationCount > Validator.BUDGETS.maxAnimationCount) { warnings.push({ - code: 'DEEP_HIERARCHY', - message: `Hierarchy deeper than ${Validator.BUDGETS.maxHierarchyDepth}` + code: 'HIGH_ANIMATION_COUNT', + message: `More than ${Validator.BUDGETS.maxAnimationCount} animations` }); } @@ -1658,6 +1758,122 @@ class Validator { module.exports = {Validator}; +/***/ }, + +/***/ 508 +(module) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +class VisibleBounds { + static PIXELS_PER_BLOCK = 16; + + static #empty() { + return {width: 0, height: 0, offset: [0, 0, 0]}; + } + + static #round(value) { + return Math.round(value * 1000) / 1000; + } + + static #usableCubes(cubes) { + if (!Array.isArray(cubes)) { + return []; + } + + return cubes.filter((cube) => + cube && Array.isArray(cube.from) && Array.isArray(cube.to) + && cube.from.length === 3 && cube.to.length === 3 + && [...cube.from, ...cube.to].every((value) => Number.isFinite(value))); + } + + static #bounds(cubes) { + const bounds = { + min: [Infinity, Infinity, Infinity], + max: [-Infinity, -Infinity, -Infinity] + }; + cubes.forEach((cube) => { + for (let axis = 0; axis < 3; axis++) { + bounds.min[axis] = Math.min(bounds.min[axis], cube.from[axis], + cube.to[axis]); + bounds.max[axis] = Math.max(bounds.max[axis], cube.from[axis], + cube.to[axis]); + } + }); + + return bounds; + } + + static derive(cubes) { + const usable = VisibleBounds.#usableCubes(cubes); + if (usable.length === 0) { + return VisibleBounds.#empty(); + } + + const ppb = VisibleBounds.PIXELS_PER_BLOCK; + const bounds = VisibleBounds.#bounds(usable); + const xExtent = bounds.max[0] - bounds.min[0]; + const yExtent = bounds.max[1] - bounds.min[1]; + const zExtent = bounds.max[2] - bounds.min[2]; + const width = Math.max(xExtent, zExtent) / ppb; + const height = yExtent / ppb; + + if (width <= 0 || height <= 0) { + return VisibleBounds.#empty(); + } + + return { + width: VisibleBounds.#round(width), + height: VisibleBounds.#round(height), + offset: [ + VisibleBounds.#round(((bounds.min[0] + bounds.max[0]) / 2) / ppb), + VisibleBounds.#round(bounds.min[1] / ppb), + VisibleBounds.#round(((bounds.min[2] + bounds.max[2]) / 2) / ppb) + ] + }; + } + + static hasVisibleBounds(settings) { + const rendering = settings && settings.rendering; + return !!rendering && rendering.visibleBoundsWidth > 0 + && rendering.visibleBoundsHeight > 0; + } + + static applyVisibleBounds(settings, derived, options) { + if (!derived || !settings || !settings.rendering) { + return settings; + } + if (options?.preserveExisting && VisibleBounds.hasVisibleBounds(settings)) { + return settings; + } + + settings.rendering.visibleBoundsWidth = derived.width; + settings.rendering.visibleBoundsHeight = derived.height; + settings.rendering.visibleBoundsOffset = derived.offset.slice(); + return settings; + } +} + +module.exports = {VisibleBounds}; + + /***/ }, /***/ 151 @@ -1682,25 +1898,40 @@ module.exports = {Validator}; * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Mirrors de.markusbordihn.easymodelentities.profile.ModelPresetType and the -// per-preset defaults derived by EasyModelProfileParser / ModelRenderProfileParser. -// This file is the single source of truth the plugin uses to compute minimal -// (diff-based) profiles, so it must stay in sync with the mod. +// Mirrors de.markusbordihn.easymodelentities.profile.ModelPresetType. const SCHEMA_VERSION = '0.1.0'; -// Mirrors de.markusbordihn.easymodelentities.data.profile.ModelType. const MODEL_TYPE_ENTITY = 'entity'; const MODEL_TYPE_BLOCK_ENTITY = 'block_entity'; -const MODEL_TYPES = (/* unused pure expression or super */ null && ([MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY])); +const MODEL_TYPES = [MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY]; + +const BODY_TYPES = [ + 'static', + 'biped', + 'quadruped', + 'aquatic', + 'amphibious', + 'winged', + 'winged_humanoid', + 'arthropod', + 'cuboid', + 'floating' +]; + +const MOVEMENT_TYPES = ['ground', 'water', 'amphibious', 'static']; +const BEHAVIOR_MODES = ['idle_only', 'ambient', 'static', 'external_owner']; +const ANIMATION_MODES = ['automatic', 'random_idle', 'none']; +const GAIT_TYPES = ['natural', 'feline', 'ungulate']; const GROUND_ENTITY = 'easy_model_entities:ground_entity'; const STATIC_ENTITY = 'easy_model_entities:static_entity'; +const AQUATIC_ENTITY = 'easy_model_entities:aquatic_entity'; +const AMPHIBIOUS_ENTITY = 'easy_model_entities:amphibious_entity'; const FALLBACK_DIMENSIONS = {width: 0.6, height: 1.8, eyeHeight: 1.62}; const BLOCK_ENTITY_DIMENSIONS = {width: 1.0, height: 1.0, eyeHeight: 0.5}; -// All ModelPresetType values in serialized form. const PRESET_TYPES = [ 'custom', 'static', @@ -1711,6 +1942,8 @@ const PRESET_TYPES = [ 'quadruped_wandering', 'aquatic_still', 'aquatic_swimming', + 'amphibious_still', + 'amphibious_wandering', 'winged_still', 'winged_wandering', 'winged_humanoid_still', @@ -1722,7 +1955,6 @@ const PRESET_TYPES = [ 'floating_still' ]; -// All ModelBlockEntityPresetType values in serialized form. const BLOCK_ENTITY_PRESET_TYPES = [ 'static', 'ticking', @@ -1730,8 +1962,6 @@ const BLOCK_ENTITY_PRESET_TYPES = [ 'animated_randomly' ]; -// Presets the mod marks as stable for pack authors; experimental ones are hidden -// in the UI behind the "Show experimental presets" setting. const STABLE_PRESET_TYPES = new Set([ 'static', 'statue', @@ -1739,12 +1969,24 @@ const STABLE_PRESET_TYPES = new Set([ 'humanoid_wandering', 'quadruped_still', 'quadruped_wandering', + 'aquatic_still', + 'aquatic_swimming', + 'amphibious_still', + 'amphibious_wandering', + 'winged_still', + 'winged_wandering', + 'winged_humanoid_still', + 'winged_humanoid_wandering', + 'arthropod_still', + 'arthropod_wandering', + 'cuboid_still', + 'cuboid_hopping', + 'floating_still', // Block entity stable presets. 'animated', 'animated_randomly' ]); -// Presets selectable as a starting point in the UI (custom handled separately). const SELECTABLE_PRESET_TYPES = PRESET_TYPES.filter((id) => id !== 'custom'); function isStablePreset(presetType) { @@ -1776,6 +2018,9 @@ function bodyType(presetType) { case 'aquatic_still': case 'aquatic_swimming': return 'aquatic'; + case 'amphibious_still': + case 'amphibious_wandering': + return 'amphibious'; case 'winged_still': case 'winged_wandering': return 'winged'; @@ -1795,11 +2040,34 @@ function bodyType(presetType) { } } +function isAquatic(presetType) { + return presetType === 'aquatic_still' || presetType === 'aquatic_swimming'; +} + +function isAmphibious(presetType) { + return presetType === 'amphibious_still' + || presetType === 'amphibious_wandering'; +} + function movementType(presetType) { + if (isAquatic(presetType)) { + return 'water'; + } + if (isAmphibious(presetType)) { + return 'amphibious'; + } + return isMoving(presetType) ? 'ground' : 'static'; } function entityType(presetType) { + if (isAquatic(presetType)) { + return AQUATIC_ENTITY; + } + if (isAmphibious(presetType)) { + return AMPHIBIOUS_ENTITY; + } + return isMoving(presetType) || presetType === 'static' ? GROUND_ENTITY : STATIC_ENTITY; } @@ -1812,6 +2080,9 @@ function presetDimensions(presetType) { case 'aquatic_still': case 'aquatic_swimming': return {width: 0.7, height: 0.4, eyeHeight: 0.25}; + case 'amphibious_still': + case 'amphibious_wandering': + return {width: 0.9, height: 0.6, eyeHeight: 0.4}; case 'winged_still': case 'winged_wandering': return {width: 0.6, height: 0.9, eyeHeight: 0.6}; @@ -1830,11 +2101,12 @@ function presetDimensions(presetType) { } } -// Shadow radius the mod derives per render preset (ModelRenderSettings). function presetShadowRadius(presetType) { switch (presetType) { case 'quadruped_still': case 'quadruped_wandering': + case 'amphibious_still': + case 'amphibious_wandering': return 0.45; case 'aquatic_still': case 'aquatic_swimming': @@ -1855,8 +2127,6 @@ function presetShadowRadius(presetType) { } } -// Behavior mode the mod derives for a preset, given the resolved movement type -// (only relevant for the custom preset where movement type can vary). function behaviorModeFor(presetType, move) { if (isMoving(presetType)) { return 'ambient'; @@ -1867,7 +2137,7 @@ function behaviorModeFor(presetType, move) { } if (presetType === 'custom') { - return move === 'ground' ? 'idle_only' : 'static'; + return move === 'ground' || move === 'amphibious' ? 'idle_only' : 'static'; } return 'static'; @@ -1877,13 +2147,28 @@ function behaviorMode(presetType) { return behaviorModeFor(presetType, movementType(presetType)); } -// Movement defaults the mod derives from the resolved movement type. +function wandersByMovement(move) { + return move === 'ground' || move === 'amphibious' || move === 'water'; +} + +function defaultSpeed(move) { + switch (move) { + case 'ground': + return 0.22; + case 'amphibious': + return 0.15; + case 'water': + return 0.1; + default: + return 0; + } +} + function movementDefaults(presetType, move) { - const ground = move === 'ground'; return { - speed: ground ? 0.22 : 0, - stepHeight: ground ? 0.6 : 0, - gravity: presetType === 'static' || ground + speed: defaultSpeed(move), + stepHeight: move === 'ground' || move === 'amphibious' ? 0.6 : 0, + gravity: presetType === 'static' || move !== 'static' }; } @@ -1892,7 +2177,6 @@ function animationMode(presetType) { || presetType === 'statue' ? 'none' : 'automatic'; } -// Animation mode the mod derives for a block entity preset. function blockEntityAnimationMode(presetType) { switch (presetType) { case 'animated': @@ -1904,8 +2188,26 @@ function blockEntityAnimationMode(presetType) { } } -// Block entities ignore movement/behavior/attributes; the host is always a -// static cuboid block. +function defaultRenderingSettings(presetType) { + return { + scale: 1, + shadowRadius: presetShadowRadius(presetType), + visibleBoundsWidth: 0, + visibleBoundsHeight: 0, + visibleBoundsOffset: [0, 0, 0] + }; +} + +function defaultAnimationSettings(mode) { + return { + mode: mode, + swingSpeed: 1, + walkSpeedMultiplier: 1, + idleStrength: 1, + gait: 'natural' + }; +} + function blockEntityPresetDefaults(presetType) { return { schemaVersion: SCHEMA_VERSION, @@ -1916,12 +2218,8 @@ function blockEntityPresetDefaults(presetType) { movement: {speed: 0, stepHeight: 0, gravity: false}, behavior: {mode: 'static', lookAtPlayers: false, randomStroll: false}, attributes: {maxHealth: 10, movementSpeed: 0, followRange: 16}, - rendering: {scale: 1, shadowRadius: 0.5}, - animation: { - mode: blockEntityAnimationMode(presetType), - swingSpeed: 1, - walkSpeedMultiplier: 1 - } + rendering: defaultRenderingSettings('cuboid_still'), + animation: defaultAnimationSettings(blockEntityAnimationMode(presetType)) }; } @@ -1931,8 +2229,7 @@ function presetDefaults(presetType, modelType) { } const move = movementType(presetType); - const ground = move === 'ground'; - const speed = ground ? 0.22 : 0; + const movement = movementDefaults(presetType, move); const mode = behaviorMode(presetType); return { @@ -1946,22 +2243,22 @@ function presetDefaults(presetType, modelType) { }, dimensions: presetDimensions(presetType), movement: { - speed: speed, - stepHeight: ground ? 0.6 : 0, - gravity: presetType === 'static' || ground + speed: movement.speed, + stepHeight: movement.stepHeight, + gravity: movement.gravity }, behavior: { mode: mode, lookAtPlayers: mode === 'idle_only' || mode === 'ambient', - randomStroll: ground && mode === 'ambient' + randomStroll: wandersByMovement(move) && mode === 'ambient' }, - attributes: {maxHealth: 10, movementSpeed: speed, followRange: 16}, - rendering: {scale: 1, shadowRadius: presetShadowRadius(presetType)}, - animation: { - mode: animationMode(presetType), - swingSpeed: 1, - walkSpeedMultiplier: 1 - } + attributes: { + maxHealth: 10, + movementSpeed: movement.speed, + followRange: 16 + }, + rendering: defaultRenderingSettings(presetType), + animation: defaultAnimationSettings(animationMode(presetType)) }; } @@ -1969,14 +2266,21 @@ module.exports = { SCHEMA_VERSION, MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY, + MODEL_TYPES, + BODY_TYPES, + MOVEMENT_TYPES, + BEHAVIOR_MODES, PRESET_TYPES, BLOCK_ENTITY_PRESET_TYPES, + ANIMATION_MODES, + GAIT_TYPES, SELECTABLE_PRESET_TYPES, isStablePreset, isCustom, bodyType, behaviorModeFor, movementDefaults, + wandersByMovement, animationMode, presetDimensions, presetShadowRadius, @@ -2014,9 +2318,42 @@ const { MODEL_TYPE_ENTITY, presetDefaults } = __webpack_require__(151); +const {getDefaultVersionId} = __webpack_require__(954); const DEFAULT_PRESET = 'statue'; +const MODEL_SETTING_KEYS = [ + 'schemaVersion', + 'modelType', + 'presetType', + 'namespace', + 'profileId', + 'targetVersion', + 'customize', + 'host', + 'dimensions', + 'movement', + 'behavior', + 'attributes', + 'rendering', + 'animation' +]; + +function pickModelSettings(settings) { + if (!settings || typeof settings !== 'object') { + return settings; + } + + const picked = {}; + MODEL_SETTING_KEYS.forEach((key) => { + if (settings[key] !== undefined) { + picked[key] = settings[key]; + } + }); + + return picked; +} + function deepMerge(target, source) { const result = Array.isArray(target) ? target.slice() : ({ ...target @@ -2039,13 +2376,10 @@ function getDefaults() { return deepMerge(presetDefaults(DEFAULT_PRESET), { namespace: 'example_org', profileId: 'entity', - targetVersion: '1.20.1' + targetVersion: getDefaultVersionId() }); } -// Full settings object for a preset type, including the identity fields. The -// model type disambiguates presets that exist for both entities and block -// entities (e.g. "static"). function applyTemplate(presetType, modelType) { return deepMerge(getDefaults(), presetDefaults(presetType, modelType || MODEL_TYPE_ENTITY)); @@ -2057,7 +2391,8 @@ module.exports = { SELECTABLE_PRESET_TYPES, getDefaults, applyTemplate, - deepMerge + deepMerge, + pickModelSettings }; @@ -2091,30 +2426,30 @@ const VERSIONS = [ { id: '1.20.1', label: 'Minecraft 1.20.1', - dataFormat: 15, - resourceFormat: 15, + data: {packFormat: 15}, + resource: {packFormat: 15}, enabled: true }, { id: '1.21.1', label: 'Minecraft 1.21.1', - dataFormat: 48, - resourceFormat: 34, - enabled: false + data: {packFormat: 48}, + resource: {packFormat: 34}, + enabled: true }, { id: '1.21.11', label: 'Minecraft 1.21.11', - dataFormat: null, - resourceFormat: null, - enabled: false + data: {packFormat: 94, minFormat: [94, 1], maxFormat: [94, 1]}, + resource: {packFormat: 75, minFormat: [75, 0], maxFormat: [75, 0]}, + enabled: true }, { id: '26.1.2', label: 'Minecraft 26.1.2', - dataFormat: null, - resourceFormat: null, - enabled: false + data: {packFormat: 101, minFormat: [101, 1], maxFormat: [101, 1]}, + resource: {packFormat: 84, minFormat: [84, 0], maxFormat: [84, 0]}, + enabled: true } ]; @@ -2136,12 +2471,12 @@ function getDefaultVersionId() { function getPackFormats(id) { const version = getVersion(id); - if (!version?.enabled) { + if (!version?.enabled || !version.data || !version.resource) { return null; } return { - dataFormat: version.dataFormat, - resourceFormat: version.resourceFormat + data: version.data, + resource: version.resource }; } @@ -2177,119 +2512,30 @@ module.exports = { * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const {getVersions} = __webpack_require__(954); const {getDefaults, applyTemplate, deepMerge} = __webpack_require__(668); const { - SELECTABLE_PRESET_TYPES, - BLOCK_ENTITY_PRESET_TYPES, - isStablePreset, MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY } = __webpack_require__(151); const {ModelDimensions} = __webpack_require__(763); +const {VisibleBounds} = __webpack_require__(508); const {t} = __webpack_require__(16); - -function presetLabel(id) { - return id === 'custom' ? t('eme.preset.custom') : t(`eme.preset.${id}`); -} - -function entityPresetIds(experimental) { - return experimental - ? ['custom', ...SELECTABLE_PRESET_TYPES] - : SELECTABLE_PRESET_TYPES.filter(isStablePreset); -} - -function blockPresetIds(experimental) { - return experimental - ? BLOCK_ENTITY_PRESET_TYPES.slice() - : BLOCK_ENTITY_PRESET_TYPES.filter(isStablePreset); -} - -// Experimental (WIP) presets are hidden unless the experimental setting is on. -// `ensure` keeps an already-selected preset visible even when experimental is -// off, so reopening a project that used one never breaks the dropdown. -function presetOptions(modelType, experimental, ensure) { - const ids = modelType === MODEL_TYPE_BLOCK_ENTITY - ? blockPresetIds(experimental) : entityPresetIds(experimental); - if (ensure && !ids.includes(ensure)) { - ids.push(ensure); - } - const options = {}; - ids.forEach((id) => { - options[id] = presetLabel(id); - }); - - return options; -} - -function modelTypeOptions() { - return { - [MODEL_TYPE_ENTITY]: t('eme.modelType.entity'), - [MODEL_TYPE_BLOCK_ENTITY]: t('eme.modelType.block_entity') - }; -} - -function exportTypeOptions() { - return { - packs: t('eme.exportType.packs'), - mod_project: t('eme.exportType.mod_project'), - model_only: t('eme.exportType.model_only') - }; -} - -function hostEntityTypeOptions() { - return { - 'easy_model_entities:ground_entity': t('eme.entity.ground_entity'), - 'easy_model_entities:static_entity': t('eme.entity.static_entity') - }; -} - -function movementTypeOptions() { - return {ground: t('eme.movement.ground'), static: t('eme.movement.static')}; -} - -function bodyTypeOptions() { - return { - static: t('eme.body.static'), - biped: t('eme.body.biped'), - quadruped: t('eme.body.quadruped'), - aquatic: t('eme.body.aquatic'), - winged: t('eme.body.winged'), - winged_humanoid: t('eme.body.winged_humanoid'), - arthropod: t('eme.body.arthropod'), - cuboid: t('eme.body.cuboid'), - floating: t('eme.body.floating') - }; -} - -function behaviorModeOptions() { - return { - idle_only: t('eme.behavior.idle_only'), - ambient: t('eme.behavior.ambient'), - static: t('eme.behavior.static'), - external_owner: t('eme.behavior.external_owner') - }; -} - -function animationModeOptions() { - return { - automatic: t('eme.animation.automatic'), - random_idle: t('eme.animation.random_idle'), - none: t('eme.animation.none') - }; -} - -function versionOptions() { - const options = {}; - getVersions().forEach((version) => { - options[version.id] = version.enabled ? version.label - : `${version.label} (coming soon)`; - }); - - return options; -} +const { + presetOptions, + modelTypeOptions, + exportTypeOptions, + hostEntityTypeOptions, + movementTypeOptions, + bodyTypeOptions, + gaitOptions, + behaviorModeOptions, + animationModeOptions, + versionOptions +} = __webpack_require__(664); function settingsToForm(settings) { + const offset = settings.rendering.visibleBoundsOffset || [0, 0, 0]; + return { namespace: settings.namespace, profileId: settings.profileId, @@ -2309,9 +2555,16 @@ function settingsToForm(settings) { followRange: settings.attributes.followRange, scale: settings.rendering.scale, shadowRadius: settings.rendering.shadowRadius, + visibleBoundsWidth: settings.rendering.visibleBoundsWidth ?? 0, + visibleBoundsHeight: settings.rendering.visibleBoundsHeight ?? 0, + visibleBoundsOffsetX: offset[0], + visibleBoundsOffsetY: offset[1], + visibleBoundsOffsetZ: offset[2], animationMode: settings.animation.mode, swingSpeed: settings.animation.swingSpeed, - walkSpeedMultiplier: settings.animation.walkSpeedMultiplier + walkSpeedMultiplier: settings.animation.walkSpeedMultiplier, + idleStrength: settings.animation.idleStrength ?? 1, + gait: settings.animation.gait || 'natural' }; } @@ -2350,19 +2603,25 @@ function formToSettings(form, base) { }, rendering: { scale: Number(form.scale), - shadowRadius: Number(form.shadowRadius) + shadowRadius: Number(form.shadowRadius), + visibleBoundsWidth: Number(form.visibleBoundsWidth), + visibleBoundsHeight: Number(form.visibleBoundsHeight), + visibleBoundsOffset: [ + Number(form.visibleBoundsOffsetX), + Number(form.visibleBoundsOffsetY), + Number(form.visibleBoundsOffsetZ) + ] }, animation: { mode: form.animationMode, swingSpeed: Number(form.swingSpeed), - walkSpeedMultiplier: Number(form.walkSpeedMultiplier) + walkSpeedMultiplier: Number(form.walkSpeedMultiplier), + idleStrength: Number(form.idleStrength), + gait: form.gait } }; } -// Active preset depends on whether a block entity datapack is being authored. -// Model-only export still authors a render (body) preset, so it uses the entity -// preset list. function activeModelType(form) { return form.exportType === 'model_only' ? MODEL_TYPE_ENTITY : (form.modelType || MODEL_TYPE_ENTITY); @@ -2373,9 +2632,12 @@ function activePreset(form) { ? (form.blockPreset || 'static') : (form.preset || 'custom'); } -function presetFormValues(presetType, modelType, modelDimensions) { - const form = settingsToForm(ModelDimensions.applyModelDimensions( - applyTemplate(presetType, modelType), modelDimensions)); +function presetFormValues(presetType, modelType, modelDimensions, + visibleBounds) { + const settings = ModelDimensions.applyModelDimensions( + applyTemplate(presetType, modelType), modelDimensions); + VisibleBounds.applyVisibleBounds(settings, visibleBounds); + const form = settingsToForm(settings); delete form.namespace; delete form.profileId; delete form.targetVersion; @@ -2383,7 +2645,7 @@ function presetFormValues(presetType, modelType, modelDimensions) { return form; } -function resolveExportSettings(form, base, modelDimensions) { +function resolveExportSettings(form, base, modelDimensions, visibleBounds) { const exportType = form.exportType || 'packs'; const modelType = activeModelType(form); const preset = activePreset(form); @@ -2394,6 +2656,7 @@ function resolveExportSettings(form, base, modelDimensions) { } else { settings = ModelDimensions.applyModelDimensions( applyTemplate(preset, modelType), modelDimensions); + VisibleBounds.applyVisibleBounds(settings, visibleBounds); } settings.schemaVersion = base.schemaVersion || settings.schemaVersion; @@ -2593,6 +2856,31 @@ function buildFormConfig(settings, ui) { label: t('eme.field.shadowRadius'), type: 'number', value: values.shadowRadius, step: 0.1 }, showRender), + visibleBoundsWidth: advancedField( + { + label: t('eme.field.visibleBoundsWidth'), type: 'number', + value: values.visibleBoundsWidth, step: 0.1 + }, showRender), + visibleBoundsHeight: advancedField( + { + label: t('eme.field.visibleBoundsHeight'), type: 'number', + value: values.visibleBoundsHeight, step: 0.1 + }, showRender), + visibleBoundsOffsetX: advancedField( + { + label: t('eme.field.visibleBoundsOffsetX'), type: 'number', + value: values.visibleBoundsOffsetX, step: 0.1 + }, showRender), + visibleBoundsOffsetY: advancedField( + { + label: t('eme.field.visibleBoundsOffsetY'), type: 'number', + value: values.visibleBoundsOffsetY, step: 0.1 + }, showRender), + visibleBoundsOffsetZ: advancedField( + { + label: t('eme.field.visibleBoundsOffsetZ'), type: 'number', + value: values.visibleBoundsOffsetZ, step: 0.1 + }, showRender), animation_header: advancedField( {type: 'info', text: `### ${t('eme.section.animation')}`}, @@ -2612,7 +2900,18 @@ function buildFormConfig(settings, ui) { { label: t('eme.field.walkSpeedMultiplier'), type: 'number', value: values.walkSpeedMultiplier, step: 0.1 - }, showRender) + }, showRender), + idleStrength: advancedField( + { + label: t('eme.field.idleStrength'), type: 'number', + value: values.idleStrength, step: 0.1 + }, showRender), + gait: advancedField({ + label: t('eme.field.gait'), + type: 'select', + options: gaitOptions(), + value: values.gait + }, showRender) }); return config; @@ -2621,8 +2920,7 @@ function buildFormConfig(settings, ui) { function openExportDialog(options) { const settings = options.settings; const modelDimensions = options.modelDimensions; - // Tracks the active model type + preset so advanced fields refill whenever the - // user switches between entity/block entity or picks a different preset. + const visibleBounds = options.visibleBounds; let lastKey = `${activeModelType(options)}|${activePreset(options)}`; const dialog = new Dialog({ @@ -2646,13 +2944,14 @@ function openExportDialog(options) { lastKey = key; if (modelType !== MODEL_TYPE_ENTITY || preset !== 'custom') { dialog.setFormValues( - presetFormValues(preset, modelType, modelDimensions), false); + presetFormValues(preset, modelType, modelDimensions, + visibleBounds), false); } } }, onConfirm(form) { const finalSettings = resolveExportSettings(form, settings, - modelDimensions); + modelDimensions, visibleBounds); // Records that experimental presets were available for this export. finalSettings.experimental = !!options.experimental; options.onExport(finalSettings, finalSettings.exportTarget); @@ -2673,6 +2972,247 @@ module.exports = { }; +/***/ }, + +/***/ 664 +(module, __unused_webpack_exports, __webpack_require__) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {getVersions} = __webpack_require__(954); +const { + SELECTABLE_PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + isStablePreset, + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY +} = __webpack_require__(151); +const {t} = __webpack_require__(16); + +function presetLabel(id) { + return id === 'custom' ? t('eme.preset.custom') : t(`eme.preset.${id}`); +} + +function entityPresetIds(experimental) { + return experimental + ? ['custom', ...SELECTABLE_PRESET_TYPES] + : SELECTABLE_PRESET_TYPES.filter(isStablePreset); +} + +function blockPresetIds(experimental) { + return experimental + ? BLOCK_ENTITY_PRESET_TYPES.slice() + : BLOCK_ENTITY_PRESET_TYPES.filter(isStablePreset); +} + +// Experimental (WIP) presets are hidden unless the experimental setting is on. +function presetOptions(modelType, experimental, ensure) { + const ids = modelType === MODEL_TYPE_BLOCK_ENTITY + ? blockPresetIds(experimental) : entityPresetIds(experimental); + if (ensure && !ids.includes(ensure)) { + ids.push(ensure); + } + const options = {}; + ids.forEach((id) => { + options[id] = presetLabel(id); + }); + + return options; +} + +function modelTypeOptions() { + return { + [MODEL_TYPE_ENTITY]: t('eme.modelType.entity'), + [MODEL_TYPE_BLOCK_ENTITY]: t('eme.modelType.block_entity') + }; +} + +function exportTypeOptions() { + return { + packs: t('eme.exportType.packs'), + mod_project: t('eme.exportType.mod_project'), + model_only: t('eme.exportType.model_only') + }; +} + +function hostEntityTypeOptions() { + return { + 'easy_model_entities:ground_entity': t('eme.entity.ground_entity'), + 'easy_model_entities:static_entity': t('eme.entity.static_entity'), + 'easy_model_entities:aquatic_entity': t('eme.entity.aquatic_entity'), + 'easy_model_entities:amphibious_entity': t('eme.entity.amphibious_entity') + }; +} + +function movementTypeOptions() { + return { + ground: t('eme.movement.ground'), + water: t('eme.movement.water'), + amphibious: t('eme.movement.amphibious'), + static: t('eme.movement.static') + }; +} + +function bodyTypeOptions() { + return { + static: t('eme.body.static'), + biped: t('eme.body.biped'), + quadruped: t('eme.body.quadruped'), + aquatic: t('eme.body.aquatic'), + amphibious: t('eme.body.amphibious'), + winged: t('eme.body.winged'), + winged_humanoid: t('eme.body.winged_humanoid'), + arthropod: t('eme.body.arthropod'), + cuboid: t('eme.body.cuboid'), + floating: t('eme.body.floating') + }; +} + +function gaitOptions() { + return { + natural: t('eme.gait.natural'), + feline: t('eme.gait.feline'), + ungulate: t('eme.gait.ungulate') + }; +} + +function behaviorModeOptions() { + return { + idle_only: t('eme.behavior.idle_only'), + ambient: t('eme.behavior.ambient'), + static: t('eme.behavior.static'), + external_owner: t('eme.behavior.external_owner') + }; +} + +function animationModeOptions() { + return { + automatic: t('eme.animation.automatic'), + random_idle: t('eme.animation.random_idle'), + none: t('eme.animation.none') + }; +} + +function versionOptions() { + const options = {}; + getVersions().forEach((version) => { + options[version.id] = version.enabled ? version.label + : `${version.label} (coming soon)`; + }); + + return options; +} + +module.exports = { + presetOptions, + modelTypeOptions, + exportTypeOptions, + hostEntityTypeOptions, + movementTypeOptions, + bodyTypeOptions, + gaitOptions, + behaviorModeOptions, + animationModeOptions, + versionOptions +}; + + +/***/ }, + +/***/ 317 +(module, __unused_webpack_exports, __webpack_require__) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {describeTextureSource} = __webpack_require__(834); + +let patchedVue = null; +let originalGetDescription = null; + +function texturesPanelVue() { + const panel = (typeof Panels !== 'undefined' && Panels.textures) + || (typeof Interface !== 'undefined' && Interface.Panels + && Interface.Panels.textures); + + return (panel && panel.inside_vue) || null; +} + +function patchTexturePanel(formatId) { + const vue = texturesPanelVue(); + if (!vue || typeof vue.getDescription !== 'function' + || originalGetDescription) { + return; + } + + const original = vue.getDescription.bind(vue); + patchedVue = vue; + originalGetDescription = original; + vue.getDescription = function (texture) { + const base = original(texture); + try { + if (typeof Format !== 'undefined' && Format && Format.id === formatId) { + const {label} = describeTextureSource({ + namespace: texture.namespace, + folder: texture.folder, + name: texture.name, + path: texture.path + }); + + return `${base} - ${label}`; + } + } catch (error) { + // Display nicety only; never break the panel render. + } + + return base; + }; +} + +function unpatchTexturePanel() { + if (patchedVue && originalGetDescription) { + patchedVue.getDescription = originalGetDescription; + } + patchedVue = null; + originalGetDescription = null; +} + +module.exports = {patchTexturePanel, unpatchTexturePanel}; + + /***/ }, /***/ 20 @@ -2804,26 +3344,35 @@ function customLocation(settings, index) { } function parseExternalLocation(descriptor) { - const path = String(descriptor.path || '').replace(/\\/g, '/'); - const match = path.match(ASSETS_PATTERN); - if (match) { - return `${match[1].toLowerCase()}:textures/${match[2].toLowerCase()}.png`; - } - const namespace = String(descriptor.namespace || '').trim().toLowerCase(); - if (!namespace) { - return null; + if (namespace) { + const folder = trimSlashes(String(descriptor.folder || '').trim()); + const name = stripPng(String(descriptor.name || '').trim()).toLowerCase(); + if (!name) { + return null; + } + const rest = folder ? `${folder.toLowerCase()}/${name}` : name; + + return `${namespace}:textures/${rest}.png`; } - const folder = trimSlashes(String(descriptor.folder || '').trim()); - const name = stripPng(String(descriptor.name || '').trim()).toLowerCase(); - if (!name) { - return null; + const path = String(descriptor.path || '').replace(/\\/g, '/'); + const match = path.match(ASSETS_PATTERN); + if (match && match[1].toLowerCase() === 'minecraft') { + return `minecraft:textures/${match[2].toLowerCase()}.png`; } - const rest = folder ? `${folder.toLowerCase()}/${name}` : name; + return null; +} - return `${namespace}:textures/${rest}.png`; +function describeTextureSource(descriptor) { + const location = parseExternalLocation(descriptor); + return location + ? { + external: true, label: location.replace(':textures/', ':').replace( + /\.png$/i, '') + } + : {external: false, label: 'Custom Texture'}; } function resolveTextures(descriptors, settings) { @@ -2844,8 +3393,6 @@ function resolveTextures(descriptors, settings) { }); } - // Index 0 follows the mod's default path unless it deviates (e.g. vanilla); - // every other index is always spelled out in the textures map. if (index === 0) { if (location !== defaultLocation) { texture = location; @@ -2858,7 +3405,11 @@ function resolveTextures(descriptors, settings) { return {texture, textures, packed}; } -module.exports = {resolveTextures, parseExternalLocation}; +module.exports = { + resolveTextures, + parseExternalLocation, + describeTextureSource +}; /***/ }, @@ -2996,9 +3547,11 @@ const { DEFAULT_PRESET } = __webpack_require__(668); const {ModelDimensions} = __webpack_require__(763); +const {VisibleBounds} = __webpack_require__(508); const {PresetDetector} = __webpack_require__(858); const {ResourceLocation} = __webpack_require__(20); const {resolveTextures} = __webpack_require__(834); +const {patchTexturePanel, unpatchTexturePanel} = __webpack_require__(317); const {Validator} = __webpack_require__(229); const {buildPackBundle, buildModProjectFiles} = __webpack_require__(869); const {BlockbenchAdapter} = __webpack_require__(833); @@ -3042,6 +3595,7 @@ function resolveDialogState() { const storedSettings = BlockbenchAdapter.loadSettings(); const stats = BlockbenchAdapter.getModelStats(); const bounds = BlockbenchAdapter.getModelBounds(); + const visibleBounds = VisibleBounds.derive(BlockbenchAdapter.getModelCubes()); let settings; let preset; @@ -3075,8 +3629,6 @@ function resolveDialogState() { exportType = 'packs'; } - // Derive the profile id from the model name/file whenever none has been set - // yet (empty or still the placeholder default). const projectName = BlockbenchAdapter.getProjectName(); if (projectName && (!settings.profileId || settings.profileId @@ -3084,8 +3636,13 @@ function resolveDialogState() { settings.profileId = ResourceLocation.sanitizeProfileId(projectName); } + VisibleBounds.applyVisibleBounds(settings, visibleBounds, { + preserveExisting: !!storedSettings + }); + return { settings, preset, blockPreset, modelType, customize, exportType, + visibleBounds, modelDimensions: ModelDimensions.deriveDimensions(bounds, settings.host.bodyType) }; @@ -3222,6 +3779,7 @@ function openDialog() { showCustomization: customizationEnabled(), experimental: experimentalEnabled(), modelDimensions: state.modelDimensions, + visibleBounds: state.visibleBounds, onExport: runExport }); } @@ -3281,8 +3839,11 @@ BBPlugin.register('easy_model_entities', { }); MenuBar.addAction(exportAction, 'file.export'); + + patchTexturePanel(FORMAT_ID); }, onunload() { + unpatchTexturePanel(); unregisterEmeCodecHooks(); unregisterEmeFormat(); if (exportAction) { diff --git a/plugins/easy_model_entities/icon.png b/plugins/easy_model_entities/icon.png index 7e7bf10a3ea50f305adc8969e8010b9b87a8b375..7c6f764e34a9cb76c939a648f57f4116c05dfe6e 100644 GIT binary patch literal 2597 zcmYk7c{CK<8^`YqV+#%05>gl%3^Gcxj1kS)8d7$tkVc5Iq&EgpqwG>rmSh>Bv9B?9 ziYS%QYnf0AgYX)=-}E}?_xqjm-0%H9&;2~l{pX%@$pp*O0(_Eu0003z?vyoqcm1v9`4({xbj9%8D==;gW`&Ut6o7pA|P$FsZ5)dXek-QrS2d zIl24~b?h7IU2jnLyK@hbaBYR{ot@F&iw%?C3x-Fc-uFx876@d&lrcmq+9Q~()xO!; zv-NK@im6J4#TY-NX^axW8c{tyak;sb`pH7q1&#Gba>y&`gebI3{Uo+^aA&@-O+ErW zgjil*_v`GosBQ2mIv!qiEa0`~#pg(Dd6jlq#gleJpF&lwOGjk!iuxpks+;@(bFTB( zbn2Hu+c!;l?@i-r1{%&tY^0*rRr$gBg{-&6_v&@65|pi?ktci<4^xV&zB+`I9XobM z?Q*29<87UWVW-Sy)AR;>YBkO@6uI|#7BzEBHZ0}oPZBH(8uBdO$O|>P+ z?&bMV94`N*sW9b+-q>(YW#)78B||l}!$Y6yURj+U>vD4y{rR_x^H$v8CIH-z@TUxI zJV)Ep$d1B>5*_VSv@(;bQa`_;A4opLJDGsJFK0o3KQgjOutuI;GG|(s8`aOx#LuRW zxQo*T>>M&jrxLksgTpk(N;S*HsK_E@g89b#rPtn%viT^R56rq+v0$xkc1$nurGMYo zYX`q#lN48NgJdgCrf*1|z;3;L!My$6ZJk~xoHX3o^cNx8Eoy&bUW74eV@C} ziiL9uNh;#4S#t{Qb_#9Urb#EGI_(h&Q7tjvDI2ad!8u;wQPJ3d>QOpmHkcP};*!|4 zG#5abb5E%c6&c>gca%iFSM3!R%6utt;5;I1hL)|moW9ta`<)KcJ?|@u3mGEL$}II# z$ZmRob!M;cg_mttuHQPzSF24PD~22#dfH;c8cC@yRe;VNiFs%c96oYl3-0zT!a>2z z@`GMX;4G6%DC-{xLv9ICyyOq$$-ZH*1lKJ9YVuY_0O^p(LOY~xqsS-7In%jo?4+fBNb4CR<_!6?JQ-pSBM~D!)ob0P8}DNT zM;l5+0~Ix)F?(3^XShh4*%BWmb$^lKby>&hdID763%4|;8ck4lhf4FE`?DJKXnCtX(pyaG&bFamB1^Q>*(K{pPZSt_m<++}EA_1UZhk$j^Do z6WS0w@s0Bdo*%?AZnkms`Qc3k86U$-5+^@GFog=-PKofWjB`_bH9Iw0dol2K?Di~h zp1EwdlfDF=B$!3b58DH8Q$hxbE8p-zOTe9JbMUSSFao{|hG+1C9uHn{^X6<^>P=GZ z8h*!u>;a}d@yy5{imk4-cLDtgN>sKi1#DolbOcYpfn7+L)|MQmIAb6l#)Ac}IU`HZ ziq)$abje=&w=!AGW7eCl6M2&Uu=+QVaTM9c4vx)@kxv8TCdi;Ya~v|5rMS}KCi_e^ zn*Qs9()a6y#v#|#9Mihc#|4!gmmmckkum{omW29-#!vco!JX|}Wa~6>hJBBF*%hY` z2w&^Ufi!N~{%eQUH`5-Q- zb{uJjB{o^qauj9VLi+aJ1A(3oQKies$di+YrA-PeaFvSS2P>JMTxerqpx^2v!rQN7 zbP#@Y9QRxN4hrpRkW(}Gs#!w^B}h7>U2pl?B{TY0&Pw9)3Z?M@AkPBxfAw59{n(x&3aXH!31{{+Ug!Y){ z_~&Ya55+5Sh8Am^A{bkvsJ!ACnVK?^E$df|cik;%_}{iM&}Hp!&wkus;3%WkX9m#P zXtLkmUqz~?1Dt$_46#NR6@b1pZOGHi*huS_@9_3Q=QFFvlpjBUng?k*tE>Z-5h@L&f2GM|p@n`Mk4I=t&gbtq3qEueT9q3&qyW=gncd?%aJ)3@e?cA4ChS$tFe;u_IGhPyH6i&*mi{oArZVJ34dt zAN7#~WJ*6BaFBeUTXKwL;(qPv%4@EhIX-CSxyv+BYbbcAkAq9myc8v|NH-eED+yGf z&WU)?w4QcOt&&PSAn5Oc1#&-r+@njuJ3Q&%y#dBYQ@x4#mJbXXb4#zR=-bly&e?1o zC!s{uZ_Q8+D}wC!H7S$iZo1!RBX}4LJrb5%y4-jL)U7-65#oSq5$DCpLIf8R3Ic2R zV!tnbf42t!iJAux%<9Ba0CSya2qx}9>@+11GOnb1sHQtI`iud`g$a;#JEj3F2=fRC zuZRF$mlBv91qdu$em)NNE`(u@LPCt86_(VM7q2pe_@4yd5lVqj`+3arlrZthmSx$k z$L}7Ty~3%&m^%IHd<^tdeHzj~xxATn|IR_*)wu;3)4X$JIk{6r&r& za0U;Vc!_h06w}fhs!#{Y^+W~Kbu{~2jhjcA#x!vI*vfd)GXXF*WM^+HLRd1|G*k-7EVmc57Ib$~3=jmKEdq!pw4Q2(wh?jsbl7t>4eE#;CZn#VmRjk zRYpaCcCpeS)+%^^dB?DwS7$Zvn(i&cO2ClBz(mZwUv Ht`YwM?nSh} literal 5255 zcmV;26nN{2P)_{r~^}Yp5$)nkZ3`Cu*lE03Rs-{rzsLELoZ;x7knp{r+vKD_xu@TbwBV|Npt# zQC^)XZK^9(mMH)J{|_}K{QUeMRUz;8_ezT=02(U(|Ni>@{`vd-1t%y+hbLs9DLQ*5 z^!fQzlPBf!^AtQM01PK?tt?uXDF9(=PmU-6YIF!LCp2><|N8pO=;{J?fdFuN;o>_xQcv;s9lD02C-0L?`O>^=hatICv%~U?t!0@bmrtf4jW~e1!m4 zVrZl)V4^8Nbto}wCIAy1{`K|N?CincR)D!QWTh$+Kq47TAOHOSn9k97x4jWBC(z^M zIGLbDfG0hFCIA8@000o``1{4;HZgUsFX-#+QJP-kQhDMW?S?rK$i?T)f>;pUXr58X+4`Alml% zsO9Ro<>-LJ$RL!V8jqJAhmQe-j>qFyk-O z@AFQvwmYJ!MWClCo~EhIPE3C)R-7pta0Gkp*RhjJbJRZ*$AwY;dhv zoVP||kvFE#F(zy!B-Gc##;lB;oPV5(`@R}{xdE?}M*fkHwGWHCcRQj(Mr_K*#g77%136p__}$P@|+ zxDY{cwC=6dR$E7FtKCz(_uhN&z0Q62y}YcpU%#*IU&Fh(FFC(^&OP_!=7JNC$LI4H zacw>bx9#t6_emdVk+$`FNzeZQaQsHgw-VWaSo8K^5NG560B}aRx7mis*1V&8j$!{j z0ADzW{S?}o2We@FtI>;ITP18<8dJy6aS|AV#x$EBa zu`#V-j{K%?_P?-LS6*JOc<@zIAwp*3T$s>3FFyLzH8vrF?xOW^MGgQ$DazkzjLXpO z9+x2`zG=gwVGZZqv*%T8+T{PheL=_p;OiHU-@^gS#`p{3>JqKX6@=ZXWlvw>300+_v<>?V-7wDg+PSY!Ys^E{}Jd+y4}w>EQq%y*8uB8USNA7&UQ| zk9Xxq@MPrP%13WYy6w$3pRb=j^UZ@WBNaD3aG!j$CE&gN%6qIz1;wNkwe#p!Idm~& z&x>uT%g`9oThhSx(0JD{LY|&tF#w)_zP|s~S+w3b78<>&V%3AMY=(D`bSb>B|7o^_ z5`aWPo<-=lu9x94NWgx6Z=5E>SlXRW!5f9bv^C>*k5|e7FzeCh>sR*AVgUWK`p?jQ zeDLL~a>U1g)C+IkLC*lO>=Yzx?Q7Cg6rWGwD1b59&9#%-vAEs$R*|USK0ZHhyZ*rA z%K&hpe&sA%O?O0BMEhU;@y#ZLB=msAsPN!>7v6M2P89X->qoW4iS*d<9pPQtj127p zdg!c#6yIq$G4;Spvdt@w+XGgtxb@6Kx37w>P<*s@&ijX?ZsGmNdM8$c`f^!KyoFCtp8=UH9UGnbO2 zb$ajki|5WgeK3Xx9!M$uZeyJB1fKvxwq~?;>4#W=G-z$t-7lx6hWJu6VEXuNw>|tY z%!3VJ#fm-Cx9gs}lK}49f9~8v2Tdksp?S(Cv`2T7h@-?lLzjN^$dfH=aP>KG_kp!0 z%ZfVp;b)&sx&h2%tFz$$tjnihfqZ^iZr1I*w0hT2uMFSsl;fdJ4KPalM3Z~nj` zYk_I`rcdIQlwUhFHPu_3hNP5b@!Rtj44V-i`ilOBV0V$5$i>agZD8J&momkZFBapL z6@Wxto@!V}MjWEAT)V?)gd$-H`06`0<`=EFY{O-@PM>!bxw(6QeGGVx0L$Q{GJkg^m5_z=Q^ZEv{jqLp(S0=&H37`eFkfd}-fz+7P= zgb0KIc-S?k+YaZ`%@D&n0;ty0FAb_`GfeH;62t(0`EV8;oDD0cuL{U2C}-+p~^0Uc2_D? zo6o=7umN&1t02>P23CMO2T-yA^wjZZk01a2i+jKWaERi`WfHs!hzF6^62L^UP{>ZM zg!1GiF2CNe?$Dm;rC9}F*x8vpum>(60+_duNqY%j!rLr?=wJZigIHto#K^}9pt^|z z(6tf8tX=op!%GWhgJBNfZUOKi9*{ZQd=r?p-M$kKghJeB3bNdalSbv}zNO9}Fiwli z0T>6lxXdR2tIdENKoY_Ng6sg0#3z?}a{$@{4@VhGg$c)$%xEEYQg@Iy2VAa^S}y#5I4!Rm@Hxdb6}z2 zYg-RbepstHateI7hW0>xOb4UZxsyOeVf#^w-M|9?Sa0QR0c0Jp9&{1FJ0%p9?Z$VH z9^mmd({r%b-yZ|iEC4GT0P`IIY`5P+^Ez0xmh?VZO}|`z6l#>XxIGB~w8?pDvfvc) zQStHf=DWc{3*{ymUmXC*O11$sl9%4n%H!QsjOQR5$3&oD06r>#q9a@6@+SbU$+(rM zVXA4-1fL%h10kv*z+38sLh!&D0K@}kk`ol+Sst`t4nDFjJ@x?bfCVhSmjFhYJ@;z> zxM>cAhym!%()OLzt_3V$o+Q&K3Y3#O-G0Ku=#x3r6`A4d}yJgaL~c*nWo#w(mV-UwE(?aHS)F&6n;( z?QXgyUe09C{gJg*2vO86>tP)LV77C%Psie(oN0kb=F>dbC8ro0E&2>LI7pRKVIeIswRN>QQ(0s!0y~cGC9`P z6av;-UZ(phN1B}S*l(hh&CW@)X@PZI<>Cd3ZBT|FgGAxUI@JevT8GFU06G+z{=|+F z9;($-bWKm}o+zqGLo0w6dbe_f7ppU4dtx^1BX)s3H-8~|*cLPOnc=|}GGIq8cK z(-(AjWZ96fNi0t5@do4G-ZN(kRQ|Bm{Iak_%IpNy#s@zA_(v169Bx;%J#dFT!1|z)%9AGE zZOG81pOgvbbo&mSj*Eovw3ONNv{UU6A}~KYGaw*5AztOL^SL}Bp)+|PFz}KTI7fQ! z=T{IS_8bCA4!3HKYVg;5p75~~CST-KRF|PWy@)-eLp@1LF~Hk-@9hby@~DK)&dvk? z1P+u9_=Woo_(VrXKkzE9F!pK6Glf*|j2qLeTNWWh6y7?v`1;}_ve;clZEtrP9pA#9 zr46trI(tl~Dp&cW4CdtI3?_tzb|#mV4P4k47IxM74pn*i1FvpoedYj2-t&Y(YZ%EV zTP#q|y)+;uf?7M$XPBB8!YjG^;jpmt+u~I!K?hg|Ey?LfNJuGDhKmBu1K_HLfZKJ^ z(a#*T10;@Ww39_?6jy8)jR1f=3##3*Ta&SFIPaP*FLcf;FZcK9Tyn`JOO`Cj$+;wX zu%lzKI?H)xL&H^|xpV91etY$%2kiiB(~Z3~cTkWqEMQA-A^?aUdkhWl&}wV9FTc`1 zUa>l55bVEW&xF@OZ0%P&q@tiIumGiR=v z;q8noReUr%G&7*8t@lo{Bfe`Yy>wS|Y{}?Iq8Wf^1MX62IgCF!a3EOiZU$rnVD;*Y zQ_j9IJ-0p6*%rWVwDrBcih@QYQrK=an-Q6vi0fd4F z7e8`wj@p|D04fXsSpud?X@FD;5#PdtzLT{qfG00*Vk+&NGCwyD0#KU)It+MZ@T~Jp zGa%Qh7U74!t#k{I(V`MMHNgWWfc)5eCbi~>;CzJuTr52R4Z6={0AXPO0GR;?dLkYfOr zy$2vE07^MP3;wB}%9pc%X1YfT9(Z{D9l*u|)x!10AD*SeZQB3AWs zfGyQxb)KGk>Vz!5(zu10Th>C0qPt8a`;~O z%&&N6A!3gPSpX=)6g#3BGv1rVJJEz!ZP9`d345>c6as+BH2$R00^mmjmiW+s)q`cO zu72AT#!xpu@5x3#OW1R-%mNpp}Sf>vQf{uU}CFE1kEnv-W;{rr?b zffxlVmC2wN5`1V6QU$$nBHm=&3yDAqR_$T~RMXy@5F=|ys#U>{9#F8TbG=w}v zEjx5w>(=$_)7Qg~y7aofzSgaMb#+FAp|92Ox_>;#pHoznLZA>pZ`AuRdV7GwKUn~b zkmrf;1OG$t;88fUJf+Z(B1Ctwx(P`o626rCZonO>nE@P?yF%W;gO&N)&!6gV0QMlq z{V~iNJwfDZ9|87?@BVW@%s&LM4*eAb8{zt~VcbK4@^`WW@c&(a(;qSPw= 0 ? id : position; } - // All project textures as plain descriptors for TextureResolver. External - // (vanilla / other-mod) textures are referenced by location; custom textures - // carry their PNG bytes to be packed. static collectTextures() { if (typeof Texture === 'undefined' || !Texture.all) { return []; @@ -68,6 +65,8 @@ class BlockbenchAdapter { static getModelStats() { const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; const groups = typeof Group !== 'undefined' && Group.all ? Group.all : []; + const animations = + typeof Animation !== 'undefined' && Animation.all ? Animation.all : []; const texture = BlockbenchAdapter.#firstTexture(); let maxDepth = 0; @@ -88,6 +87,7 @@ class BlockbenchAdapter { hasTexture: !!texture, cubeCount: cubes.length, boneCount: groups.length, + animationCount: animations.length, hierarchyDepth: maxDepth, boneNames: groups.map((group) => group.name), textureWidth: texture ? texture.width : undefined, @@ -127,6 +127,15 @@ class BlockbenchAdapter { return bounds; } + static getModelCubes() { + const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; + + return cubes.map((cube) => ({ + from: (cube.from || [0, 0, 0]).slice(), + to: (cube.to || [0, 0, 0]).slice() + })); + } + static getProjectName() { if (typeof Project === 'undefined' || !Project) { return ''; @@ -178,9 +187,6 @@ class BlockbenchAdapter { return zip.generateAsync({type: 'uint8array'}); } - // Wraps a ready-to-drop datapack.zip and resourcepack.zip plus the README - // into a single outer ZIP, so the user can move the inner ZIPs straight into - // their datapacks/ and resourcepacks/ folders without unpacking. static exportPackBundle(bundle, name) { return Promise.all([ BlockbenchAdapter.#zipToUint8(bundle.datapack), diff --git a/plugins/easy_model_entities/src/builders/packMeta.js b/plugins/easy_model_entities/src/builders/packMeta.js index f834c38d..593eb914 100644 --- a/plugins/easy_model_entities/src/builders/packMeta.js +++ b/plugins/easy_model_entities/src/builders/packMeta.js @@ -19,27 +19,35 @@ const {getPackFormats} = require('../model/versionMatrix'); -function buildMcmeta(settings, packFormat, description) { +function buildPack(descriptor, description) { + const pack = {pack_format: descriptor.packFormat}; + if (descriptor.minFormat) { + pack.min_format = descriptor.minFormat; + } + if (descriptor.maxFormat) { + pack.max_format = descriptor.maxFormat; + } + pack.description = description; + + return {pack}; +} + +function buildMcmeta(settings, kind, description) { const formats = getPackFormats(settings.targetVersion); if (!formats) { throw new Error( `Unsupported or disabled target version: ${settings.targetVersion}`); } - return { - pack: { - pack_format: formats[packFormat], - description: description - } - }; + return buildPack(formats[kind], description); } function buildDataPackMcmeta(settings) { - return buildMcmeta(settings, 'dataFormat', + return buildMcmeta(settings, 'data', 'Easy Model Entities server profiles'); } function buildResourcePackMcmeta(settings) { - return buildMcmeta(settings, 'resourceFormat', + return buildMcmeta(settings, 'resource', 'Easy Model Entities render assets'); } diff --git a/plugins/easy_model_entities/src/builders/renderProfile.js b/plugins/easy_model_entities/src/builders/renderProfile.js index 0b226a59..9e305249 100644 --- a/plugins/easy_model_entities/src/builders/renderProfile.js +++ b/plugins/easy_model_entities/src/builders/renderProfile.js @@ -26,20 +26,25 @@ const { } = require('../model/presetTypes'); const {diffFlat, assignIfPresent} = require('./profileDiff'); -// Block entities render as a static cuboid block, so they always use the -// "static" render preset regardless of their server-side block entity preset. function renderPresetType(settings) { return settings.modelType === MODEL_TYPE_BLOCK_ENTITY ? 'static' : settings.presetType; } function buildRendering(settings) { + const rendering = settings.rendering; return diffFlat({ - scale: settings.rendering.scale, - shadow_radius: settings.rendering.shadowRadius + scale: rendering.scale, + shadow_radius: rendering.shadowRadius, + visible_bounds_width: rendering.visibleBoundsWidth ?? 0, + visible_bounds_height: rendering.visibleBoundsHeight ?? 0, + visible_bounds_offset: rendering.visibleBoundsOffset || [0, 0, 0] }, { scale: 1, - shadow_radius: presetShadowRadius(renderPresetType(settings)) + shadow_radius: presetShadowRadius(renderPresetType(settings)), + visible_bounds_width: 0, + visible_bounds_height: 0, + visible_bounds_offset: [0, 0, 0] }); } @@ -48,16 +53,23 @@ function buildAnimation(settings) { const animation = diffFlat({ mode: settings.animation.mode, swing_speed: settings.animation.swingSpeed, - walk_speed_multiplier: settings.animation.walkSpeedMultiplier + walk_speed_multiplier: settings.animation.walkSpeedMultiplier, + idle_strength: settings.animation.idleStrength ?? 1, + gait: settings.animation.gait || 'natural' }, { mode: defaultMode, swing_speed: 1, - walk_speed_multiplier: 1 + walk_speed_multiplier: 1, + idle_strength: 1, + gait: 'natural' }); + // Animation timing is meaningless when animation is disabled. if (settings.animation.mode === 'none') { delete animation.swing_speed; delete animation.walk_speed_multiplier; + delete animation.idle_strength; + delete animation.gait; } return animation; @@ -76,11 +88,6 @@ function buildRenderProfile(settings, textureResolution) { profile.body_type = settings.host.bodyType; } - // The model and the index-0 texture follow the mod's conventional default - // path (namespace:easy_model_entities/models/id and - // namespace:textures/entity/id.png), so they are omitted whenever they match - // the default. Only deviating texture locations (e.g. vanilla index 0 or any - // index > 0) are spelled out for the mod's ModelTextureResolver. if (textureResolution && textureResolution.texture) { profile.texture = textureResolution.texture; } diff --git a/plugins/easy_model_entities/src/builders/serverProfile.js b/plugins/easy_model_entities/src/builders/serverProfile.js index 49dd11ca..42726acc 100644 --- a/plugins/easy_model_entities/src/builders/serverProfile.js +++ b/plugins/easy_model_entities/src/builders/serverProfile.js @@ -22,6 +22,7 @@ const { presetDimensions, movementDefaults, behaviorModeFor, + wandersByMovement, isCustom, MODEL_TYPE_BLOCK_ENTITY, MODEL_TYPE_ENTITY @@ -46,7 +47,7 @@ function effectiveDefaults(settings) { behavior: { mode: mode, look_at_players: mode === 'idle_only' || mode === 'ambient', - random_stroll: movementType === 'ground' && mode === 'ambient' + random_stroll: wandersByMovement(movementType) && mode === 'ambient' }, attributes: { max_health: DEFAULT_MAX_HEALTH, diff --git a/plugins/easy_model_entities/src/format/EmeCodecHooks.js b/plugins/easy_model_entities/src/format/EmeCodecHooks.js index 0576772a..2f16cebf 100644 --- a/plugins/easy_model_entities/src/format/EmeCodecHooks.js +++ b/plugins/easy_model_entities/src/format/EmeCodecHooks.js @@ -18,6 +18,7 @@ */ const {BlockbenchAdapter} = require('../BlockbenchAdapter'); +const {pickModelSettings} = require('../model/templates'); const {FORMAT_ID} = require('./EmeFormat'); const EME_SETTINGS_KEY = 'emeSettings'; @@ -29,7 +30,7 @@ function handleCompile(event) { const settings = BlockbenchAdapter.loadSettings(); if (settings) { - event.model[EME_SETTINGS_KEY] = settings; + event.model[EME_SETTINGS_KEY] = pickModelSettings(settings); } } diff --git a/plugins/easy_model_entities/src/format/EmeFormat.js b/plugins/easy_model_entities/src/format/EmeFormat.js index 61253ca9..25d34d99 100644 --- a/plugins/easy_model_entities/src/format/EmeFormat.js +++ b/plugins/easy_model_entities/src/format/EmeFormat.js @@ -43,6 +43,7 @@ function registerEmeFormat(icon) { box_uv: true, optional_box_uv: true, single_texture: false, + texture_folder: true, bone_rig: true, centered_grid: true, animated_textures: false, diff --git a/plugins/easy_model_entities/src/i18n/translations.js b/plugins/easy_model_entities/src/i18n/translations.js index 3c748a6f..d86bcdd6 100644 --- a/plugins/easy_model_entities/src/i18n/translations.js +++ b/plugins/easy_model_entities/src/i18n/translations.js @@ -45,9 +45,16 @@ const EN = { 'eme.field.followRange': 'Follow Range', 'eme.field.scale': 'Scale', 'eme.field.shadowRadius': 'Shadow Radius', + 'eme.field.visibleBoundsWidth': 'Visible Bounds Width (0 = auto)', + 'eme.field.visibleBoundsHeight': 'Visible Bounds Height (0 = auto)', + 'eme.field.visibleBoundsOffsetX': 'Visible Bounds Offset X', + 'eme.field.visibleBoundsOffsetY': 'Visible Bounds Offset Y', + 'eme.field.visibleBoundsOffsetZ': 'Visible Bounds Offset Z', 'eme.field.animationMode': 'Animation Mode', 'eme.field.swingSpeed': 'Swing Speed', 'eme.field.walkSpeedMultiplier': 'Walk Speed Multiplier', + 'eme.field.idleStrength': 'Idle Strength', + 'eme.field.gait': 'Gait', 'eme.section.host': 'Host', 'eme.section.dimensions': 'Dimensions', 'eme.section.movement': 'Movement', @@ -69,6 +76,8 @@ const EN = { 'eme.preset.quadruped_wandering': 'Quadruped (4 legs, ground, wandering)', 'eme.preset.aquatic_still': 'Aquatic (fish, still)', 'eme.preset.aquatic_swimming': 'Aquatic (fish, swimming)', + 'eme.preset.amphibious_still': 'Amphibious (land/water, still)', + 'eme.preset.amphibious_wandering': 'Amphibious (land/water, wandering)', 'eme.preset.winged_still': 'Winged (bird, perched)', 'eme.preset.winged_wandering': 'Winged (bird, flying)', 'eme.preset.winged_humanoid_still': 'Winged humanoid (standing)', @@ -82,13 +91,19 @@ const EN = { 'eme.body.biped': 'Biped (2 legs)', 'eme.body.quadruped': 'Quadruped (4 legs)', 'eme.body.aquatic': 'Aquatic', + 'eme.body.amphibious': 'Amphibious', 'eme.body.winged': 'Winged', 'eme.body.winged_humanoid': 'Winged humanoid', 'eme.body.arthropod': 'Arthropod', 'eme.body.cuboid': 'Cuboid', 'eme.body.floating': 'Floating', 'eme.movement.ground': 'Ground', + 'eme.movement.water': 'Water', + 'eme.movement.amphibious': 'Amphibious', 'eme.movement.static': 'Static', + 'eme.gait.natural': 'Natural', + 'eme.gait.feline': 'Feline (light, quick)', + 'eme.gait.ungulate': 'Ungulate (heavy, slow)', 'eme.behavior.idle_only': 'Idle only', 'eme.behavior.ambient': 'Ambient (wanders)', 'eme.behavior.static': 'Static', @@ -98,6 +113,8 @@ const EN = { 'eme.animation.none': 'None', 'eme.entity.ground_entity': 'Ground entity', 'eme.entity.static_entity': 'Static entity', + 'eme.entity.aquatic_entity': 'Aquatic entity', + 'eme.entity.amphibious_entity': 'Amphibious entity', 'eme.modelType.entity': 'Entity', 'eme.modelType.block_entity': 'Block Entity', 'eme.exportType.packs': 'Standalone: Data Pack + Resource Pack (ZIP)', @@ -129,9 +146,16 @@ const DE = { 'eme.field.followRange': 'Folgereichweite', 'eme.field.scale': 'Skalierung', 'eme.field.shadowRadius': 'Schattenradius', + 'eme.field.visibleBoundsWidth': 'Sichtbarkeitsbox-Breite (0 = auto)', + 'eme.field.visibleBoundsHeight': 'Sichtbarkeitsbox-Höhe (0 = auto)', + 'eme.field.visibleBoundsOffsetX': 'Sichtbarkeitsbox-Versatz X', + 'eme.field.visibleBoundsOffsetY': 'Sichtbarkeitsbox-Versatz Y', + 'eme.field.visibleBoundsOffsetZ': 'Sichtbarkeitsbox-Versatz Z', 'eme.field.animationMode': 'Animationsmodus', 'eme.field.swingSpeed': 'Schwunggeschwindigkeit', 'eme.field.walkSpeedMultiplier': 'Geh-Geschwindigkeitsfaktor', + 'eme.field.idleStrength': 'Leerlauf-Intensität', + 'eme.field.gait': 'Gangart', 'eme.section.host': 'Host', 'eme.section.dimensions': 'Abmessungen', 'eme.section.movement': 'Bewegung', @@ -153,6 +177,8 @@ const DE = { 'eme.preset.quadruped_wandering': 'Vierbeiner (4 Beine, Boden, umherlaufend)', 'eme.preset.aquatic_still': 'Wassertier (Fisch, ruhend)', 'eme.preset.aquatic_swimming': 'Wassertier (Fisch, schwimmend)', + 'eme.preset.amphibious_still': 'Amphibie (Land/Wasser, ruhend)', + 'eme.preset.amphibious_wandering': 'Amphibie (Land/Wasser, umherlaufend)', 'eme.preset.winged_still': 'Geflügelt (Vogel, sitzend)', 'eme.preset.winged_wandering': 'Geflügelt (Vogel, fliegend)', 'eme.preset.winged_humanoid_still': 'Geflügelter Humanoid (stehend)', @@ -166,13 +192,19 @@ const DE = { 'eme.body.biped': 'Zweibeiner (2 Beine)', 'eme.body.quadruped': 'Vierbeiner (4 Beine)', 'eme.body.aquatic': 'Wassertier', + 'eme.body.amphibious': 'Amphibie', 'eme.body.winged': 'Geflügelt', 'eme.body.winged_humanoid': 'Geflügelter Humanoid', 'eme.body.arthropod': 'Gliederfüßer', 'eme.body.cuboid': 'Quaderförmig', 'eme.body.floating': 'Schwebend', 'eme.movement.ground': 'Boden', + 'eme.movement.water': 'Wasser', + 'eme.movement.amphibious': 'Amphibisch', 'eme.movement.static': 'Statisch', + 'eme.gait.natural': 'Natürlich', + 'eme.gait.feline': 'Katzenartig (leicht, schnell)', + 'eme.gait.ungulate': 'Huftier (schwer, langsam)', 'eme.behavior.idle_only': 'Nur Leerlauf', 'eme.behavior.ambient': 'Umgebung (läuft umher)', 'eme.behavior.static': 'Statisch', @@ -182,6 +214,8 @@ const DE = { 'eme.animation.none': 'Keine', 'eme.entity.ground_entity': 'Boden-Entität', 'eme.entity.static_entity': 'Statische Entität', + 'eme.entity.aquatic_entity': 'Wasser-Entität', + 'eme.entity.amphibious_entity': 'Amphibien-Entität', 'eme.modelType.entity': 'Entität', 'eme.modelType.block_entity': 'Block-Entität', 'eme.exportType.packs': 'Standalone: Data Pack + Resource Pack (ZIP)', diff --git a/plugins/easy_model_entities/src/index.js b/plugins/easy_model_entities/src/index.js index a4fe2e59..608bbb5a 100644 --- a/plugins/easy_model_entities/src/index.js +++ b/plugins/easy_model_entities/src/index.js @@ -24,9 +24,11 @@ const { DEFAULT_PRESET } = require('./model/templates'); const {ModelDimensions} = require('./model/ModelDimensions'); +const {VisibleBounds} = require('./model/VisibleBounds'); const {PresetDetector} = require('./model/PresetDetector'); const {ResourceLocation} = require('./utils/ResourceLocation'); const {resolveTextures} = require('./utils/TextureResolver'); +const {patchTexturePanel, unpatchTexturePanel} = require('./ui/texturePanel'); const {Validator} = require('./model/Validator'); const {buildPackBundle, buildModProjectFiles} = require('./builders/exporter'); const {BlockbenchAdapter} = require('./BlockbenchAdapter'); @@ -70,6 +72,7 @@ function resolveDialogState() { const storedSettings = BlockbenchAdapter.loadSettings(); const stats = BlockbenchAdapter.getModelStats(); const bounds = BlockbenchAdapter.getModelBounds(); + const visibleBounds = VisibleBounds.derive(BlockbenchAdapter.getModelCubes()); let settings; let preset; @@ -103,8 +106,6 @@ function resolveDialogState() { exportType = 'packs'; } - // Derive the profile id from the model name/file whenever none has been set - // yet (empty or still the placeholder default). const projectName = BlockbenchAdapter.getProjectName(); if (projectName && (!settings.profileId || settings.profileId @@ -112,8 +113,13 @@ function resolveDialogState() { settings.profileId = ResourceLocation.sanitizeProfileId(projectName); } + VisibleBounds.applyVisibleBounds(settings, visibleBounds, { + preserveExisting: !!storedSettings + }); + return { settings, preset, blockPreset, modelType, customize, exportType, + visibleBounds, modelDimensions: ModelDimensions.deriveDimensions(bounds, settings.host.bodyType) }; @@ -250,6 +256,7 @@ function openDialog() { showCustomization: customizationEnabled(), experimental: experimentalEnabled(), modelDimensions: state.modelDimensions, + visibleBounds: state.visibleBounds, onExport: runExport }); } @@ -309,8 +316,11 @@ BBPlugin.register('easy_model_entities', { }); MenuBar.addAction(exportAction, 'file.export'); + + patchTexturePanel(FORMAT_ID); }, onunload() { + unpatchTexturePanel(); unregisterEmeCodecHooks(); unregisterEmeFormat(); if (exportAction) { diff --git a/plugins/easy_model_entities/src/model/Validator.js b/plugins/easy_model_entities/src/model/Validator.js index 355865f7..c6765708 100644 --- a/plugins/easy_model_entities/src/model/Validator.js +++ b/plugins/easy_model_entities/src/model/Validator.js @@ -27,10 +27,16 @@ const { class Validator { static BUDGETS = { maxTextureSize: 2048, - maxModelFileSize: 2 * 1024 * 1024, + softTextureSize: 128, + maxModelFileSizeBytes: 2 * 1024 * 1024, + softModelFileSizeBytes: 1024 * 1024, maxCubeCount: 512, + softCubeCount: 384, maxBoneCount: 128, - maxHierarchyDepth: 32 + softBoneCount: 96, + maxHierarchyDepth: 32, + softHierarchyDepth: 24, + maxAnimationCount: 16 }; static REQUIRED_BODY_PARTS = { @@ -57,6 +63,18 @@ class Validator { return true; } + static #warnIfOverBudget(warnings, value, soft, max, softWarning, + maxWarning) { + if (!Validator.#isFiniteNumber(value)) { + return; + } + if (value > max) { + warnings.push(maxWarning); + } else if (value > soft) { + warnings.push(softWarning); + } + } + static validateSettings(settings, context) { const errors = []; const warnings = []; @@ -153,38 +171,62 @@ class Validator { if (Validator.#isFiniteNumber(ctx.textureWidth) && Validator.#isFiniteNumber(ctx.textureHeight)) { - if (ctx.textureWidth > Validator.BUDGETS.maxTextureSize - || ctx.textureHeight > Validator.BUDGETS.maxTextureSize) { - warnings.push({ - code: 'LARGE_TEXTURE', - message: `Texture larger than ${Validator.BUDGETS.maxTextureSize}x${Validator.BUDGETS.maxTextureSize}` - }); - } - } - if (Validator.#isFiniteNumber(ctx.modelFileSize) && ctx.modelFileSize - > Validator.BUDGETS.maxModelFileSize) { - warnings.push( - {code: 'LARGE_MODEL', message: 'Model file larger than 2 MB'}); - } - if (Validator.#isFiniteNumber(ctx.cubeCount) && ctx.cubeCount - > Validator.BUDGETS.maxCubeCount) { - warnings.push({ - code: 'HIGH_CUBE_COUNT', - message: `More than ${Validator.BUDGETS.maxCubeCount} cubes` - }); - } - if (Validator.#isFiniteNumber(ctx.boneCount) && ctx.boneCount - > Validator.BUDGETS.maxBoneCount) { - warnings.push({ - code: 'HIGH_BONE_COUNT', - message: `More than ${Validator.BUDGETS.maxBoneCount} bones` - }); + const textureSize = Math.max(ctx.textureWidth, ctx.textureHeight); + Validator.#warnIfOverBudget(warnings, textureSize, + Validator.BUDGETS.softTextureSize, Validator.BUDGETS.maxTextureSize, + { + code: 'SOFT_TEXTURE_SIZE', + message: `Texture larger than ${Validator.BUDGETS.softTextureSize}x${Validator.BUDGETS.softTextureSize}` + }, + { + code: 'LARGE_TEXTURE', + message: `Texture larger than ${Validator.BUDGETS.maxTextureSize}x${Validator.BUDGETS.maxTextureSize}` + }); } - if (Validator.#isFiniteNumber(ctx.hierarchyDepth) && ctx.hierarchyDepth - > Validator.BUDGETS.maxHierarchyDepth) { + Validator.#warnIfOverBudget(warnings, ctx.modelFileSize, + Validator.BUDGETS.softModelFileSizeBytes, + Validator.BUDGETS.maxModelFileSizeBytes, + { + code: 'SOFT_MODEL_SIZE', + message: 'Model file larger than 1 MB' + }, + {code: 'LARGE_MODEL', message: 'Model file larger than 2 MB'}); + Validator.#warnIfOverBudget(warnings, ctx.cubeCount, + Validator.BUDGETS.softCubeCount, Validator.BUDGETS.maxCubeCount, + { + code: 'SOFT_CUBE_COUNT', + message: `More than ${Validator.BUDGETS.softCubeCount} cubes` + }, + { + code: 'HIGH_CUBE_COUNT', + message: `More than ${Validator.BUDGETS.maxCubeCount} cubes` + }); + Validator.#warnIfOverBudget(warnings, ctx.boneCount, + Validator.BUDGETS.softBoneCount, Validator.BUDGETS.maxBoneCount, + { + code: 'SOFT_BONE_COUNT', + message: `More than ${Validator.BUDGETS.softBoneCount} bones` + }, + { + code: 'HIGH_BONE_COUNT', + message: `More than ${Validator.BUDGETS.maxBoneCount} bones` + }); + Validator.#warnIfOverBudget(warnings, ctx.hierarchyDepth, + Validator.BUDGETS.softHierarchyDepth, + Validator.BUDGETS.maxHierarchyDepth, + { + code: 'SOFT_HIERARCHY_DEPTH', + message: `Hierarchy deeper than ${Validator.BUDGETS.softHierarchyDepth}` + }, + { + code: 'DEEP_HIERARCHY', + message: `Hierarchy deeper than ${Validator.BUDGETS.maxHierarchyDepth}` + }); + if (Validator.#isFiniteNumber(ctx.animationCount) + && ctx.animationCount > Validator.BUDGETS.maxAnimationCount) { warnings.push({ - code: 'DEEP_HIERARCHY', - message: `Hierarchy deeper than ${Validator.BUDGETS.maxHierarchyDepth}` + code: 'HIGH_ANIMATION_COUNT', + message: `More than ${Validator.BUDGETS.maxAnimationCount} animations` }); } diff --git a/plugins/easy_model_entities/src/model/VisibleBounds.js b/plugins/easy_model_entities/src/model/VisibleBounds.js new file mode 100644 index 00000000..27152f62 --- /dev/null +++ b/plugins/easy_model_entities/src/model/VisibleBounds.js @@ -0,0 +1,109 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +class VisibleBounds { + static PIXELS_PER_BLOCK = 16; + + static #empty() { + return {width: 0, height: 0, offset: [0, 0, 0]}; + } + + static #round(value) { + return Math.round(value * 1000) / 1000; + } + + static #usableCubes(cubes) { + if (!Array.isArray(cubes)) { + return []; + } + + return cubes.filter((cube) => + cube && Array.isArray(cube.from) && Array.isArray(cube.to) + && cube.from.length === 3 && cube.to.length === 3 + && [...cube.from, ...cube.to].every((value) => Number.isFinite(value))); + } + + static #bounds(cubes) { + const bounds = { + min: [Infinity, Infinity, Infinity], + max: [-Infinity, -Infinity, -Infinity] + }; + cubes.forEach((cube) => { + for (let axis = 0; axis < 3; axis++) { + bounds.min[axis] = Math.min(bounds.min[axis], cube.from[axis], + cube.to[axis]); + bounds.max[axis] = Math.max(bounds.max[axis], cube.from[axis], + cube.to[axis]); + } + }); + + return bounds; + } + + static derive(cubes) { + const usable = VisibleBounds.#usableCubes(cubes); + if (usable.length === 0) { + return VisibleBounds.#empty(); + } + + const ppb = VisibleBounds.PIXELS_PER_BLOCK; + const bounds = VisibleBounds.#bounds(usable); + const xExtent = bounds.max[0] - bounds.min[0]; + const yExtent = bounds.max[1] - bounds.min[1]; + const zExtent = bounds.max[2] - bounds.min[2]; + const width = Math.max(xExtent, zExtent) / ppb; + const height = yExtent / ppb; + + if (width <= 0 || height <= 0) { + return VisibleBounds.#empty(); + } + + return { + width: VisibleBounds.#round(width), + height: VisibleBounds.#round(height), + offset: [ + VisibleBounds.#round(((bounds.min[0] + bounds.max[0]) / 2) / ppb), + VisibleBounds.#round(bounds.min[1] / ppb), + VisibleBounds.#round(((bounds.min[2] + bounds.max[2]) / 2) / ppb) + ] + }; + } + + static hasVisibleBounds(settings) { + const rendering = settings && settings.rendering; + return !!rendering && rendering.visibleBoundsWidth > 0 + && rendering.visibleBoundsHeight > 0; + } + + static applyVisibleBounds(settings, derived, options) { + if (!derived || !settings || !settings.rendering) { + return settings; + } + if (options?.preserveExisting && VisibleBounds.hasVisibleBounds(settings)) { + return settings; + } + + settings.rendering.visibleBoundsWidth = derived.width; + settings.rendering.visibleBoundsHeight = derived.height; + settings.rendering.visibleBoundsOffset = derived.offset.slice(); + return settings; + } +} + +module.exports = {VisibleBounds}; diff --git a/plugins/easy_model_entities/src/model/presetTypes.js b/plugins/easy_model_entities/src/model/presetTypes.js index c15c7e28..dea053c1 100644 --- a/plugins/easy_model_entities/src/model/presetTypes.js +++ b/plugins/easy_model_entities/src/model/presetTypes.js @@ -17,25 +17,40 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Mirrors de.markusbordihn.easymodelentities.profile.ModelPresetType and the -// per-preset defaults derived by EasyModelProfileParser / ModelRenderProfileParser. -// This file is the single source of truth the plugin uses to compute minimal -// (diff-based) profiles, so it must stay in sync with the mod. +// Mirrors de.markusbordihn.easymodelentities.profile.ModelPresetType. const SCHEMA_VERSION = '0.1.0'; -// Mirrors de.markusbordihn.easymodelentities.data.profile.ModelType. const MODEL_TYPE_ENTITY = 'entity'; const MODEL_TYPE_BLOCK_ENTITY = 'block_entity'; const MODEL_TYPES = [MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY]; +const BODY_TYPES = [ + 'static', + 'biped', + 'quadruped', + 'aquatic', + 'amphibious', + 'winged', + 'winged_humanoid', + 'arthropod', + 'cuboid', + 'floating' +]; + +const MOVEMENT_TYPES = ['ground', 'water', 'amphibious', 'static']; +const BEHAVIOR_MODES = ['idle_only', 'ambient', 'static', 'external_owner']; +const ANIMATION_MODES = ['automatic', 'random_idle', 'none']; +const GAIT_TYPES = ['natural', 'feline', 'ungulate']; + const GROUND_ENTITY = 'easy_model_entities:ground_entity'; const STATIC_ENTITY = 'easy_model_entities:static_entity'; +const AQUATIC_ENTITY = 'easy_model_entities:aquatic_entity'; +const AMPHIBIOUS_ENTITY = 'easy_model_entities:amphibious_entity'; const FALLBACK_DIMENSIONS = {width: 0.6, height: 1.8, eyeHeight: 1.62}; const BLOCK_ENTITY_DIMENSIONS = {width: 1.0, height: 1.0, eyeHeight: 0.5}; -// All ModelPresetType values in serialized form. const PRESET_TYPES = [ 'custom', 'static', @@ -46,6 +61,8 @@ const PRESET_TYPES = [ 'quadruped_wandering', 'aquatic_still', 'aquatic_swimming', + 'amphibious_still', + 'amphibious_wandering', 'winged_still', 'winged_wandering', 'winged_humanoid_still', @@ -57,7 +74,6 @@ const PRESET_TYPES = [ 'floating_still' ]; -// All ModelBlockEntityPresetType values in serialized form. const BLOCK_ENTITY_PRESET_TYPES = [ 'static', 'ticking', @@ -65,8 +81,6 @@ const BLOCK_ENTITY_PRESET_TYPES = [ 'animated_randomly' ]; -// Presets the mod marks as stable for pack authors; experimental ones are hidden -// in the UI behind the "Show experimental presets" setting. const STABLE_PRESET_TYPES = new Set([ 'static', 'statue', @@ -74,12 +88,24 @@ const STABLE_PRESET_TYPES = new Set([ 'humanoid_wandering', 'quadruped_still', 'quadruped_wandering', + 'aquatic_still', + 'aquatic_swimming', + 'amphibious_still', + 'amphibious_wandering', + 'winged_still', + 'winged_wandering', + 'winged_humanoid_still', + 'winged_humanoid_wandering', + 'arthropod_still', + 'arthropod_wandering', + 'cuboid_still', + 'cuboid_hopping', + 'floating_still', // Block entity stable presets. 'animated', 'animated_randomly' ]); -// Presets selectable as a starting point in the UI (custom handled separately). const SELECTABLE_PRESET_TYPES = PRESET_TYPES.filter((id) => id !== 'custom'); function isStablePreset(presetType) { @@ -111,6 +137,9 @@ function bodyType(presetType) { case 'aquatic_still': case 'aquatic_swimming': return 'aquatic'; + case 'amphibious_still': + case 'amphibious_wandering': + return 'amphibious'; case 'winged_still': case 'winged_wandering': return 'winged'; @@ -130,11 +159,34 @@ function bodyType(presetType) { } } +function isAquatic(presetType) { + return presetType === 'aquatic_still' || presetType === 'aquatic_swimming'; +} + +function isAmphibious(presetType) { + return presetType === 'amphibious_still' + || presetType === 'amphibious_wandering'; +} + function movementType(presetType) { + if (isAquatic(presetType)) { + return 'water'; + } + if (isAmphibious(presetType)) { + return 'amphibious'; + } + return isMoving(presetType) ? 'ground' : 'static'; } function entityType(presetType) { + if (isAquatic(presetType)) { + return AQUATIC_ENTITY; + } + if (isAmphibious(presetType)) { + return AMPHIBIOUS_ENTITY; + } + return isMoving(presetType) || presetType === 'static' ? GROUND_ENTITY : STATIC_ENTITY; } @@ -147,6 +199,9 @@ function presetDimensions(presetType) { case 'aquatic_still': case 'aquatic_swimming': return {width: 0.7, height: 0.4, eyeHeight: 0.25}; + case 'amphibious_still': + case 'amphibious_wandering': + return {width: 0.9, height: 0.6, eyeHeight: 0.4}; case 'winged_still': case 'winged_wandering': return {width: 0.6, height: 0.9, eyeHeight: 0.6}; @@ -165,11 +220,12 @@ function presetDimensions(presetType) { } } -// Shadow radius the mod derives per render preset (ModelRenderSettings). function presetShadowRadius(presetType) { switch (presetType) { case 'quadruped_still': case 'quadruped_wandering': + case 'amphibious_still': + case 'amphibious_wandering': return 0.45; case 'aquatic_still': case 'aquatic_swimming': @@ -190,8 +246,6 @@ function presetShadowRadius(presetType) { } } -// Behavior mode the mod derives for a preset, given the resolved movement type -// (only relevant for the custom preset where movement type can vary). function behaviorModeFor(presetType, move) { if (isMoving(presetType)) { return 'ambient'; @@ -202,7 +256,7 @@ function behaviorModeFor(presetType, move) { } if (presetType === 'custom') { - return move === 'ground' ? 'idle_only' : 'static'; + return move === 'ground' || move === 'amphibious' ? 'idle_only' : 'static'; } return 'static'; @@ -212,13 +266,28 @@ function behaviorMode(presetType) { return behaviorModeFor(presetType, movementType(presetType)); } -// Movement defaults the mod derives from the resolved movement type. +function wandersByMovement(move) { + return move === 'ground' || move === 'amphibious' || move === 'water'; +} + +function defaultSpeed(move) { + switch (move) { + case 'ground': + return 0.22; + case 'amphibious': + return 0.15; + case 'water': + return 0.1; + default: + return 0; + } +} + function movementDefaults(presetType, move) { - const ground = move === 'ground'; return { - speed: ground ? 0.22 : 0, - stepHeight: ground ? 0.6 : 0, - gravity: presetType === 'static' || ground + speed: defaultSpeed(move), + stepHeight: move === 'ground' || move === 'amphibious' ? 0.6 : 0, + gravity: presetType === 'static' || move !== 'static' }; } @@ -227,7 +296,6 @@ function animationMode(presetType) { || presetType === 'statue' ? 'none' : 'automatic'; } -// Animation mode the mod derives for a block entity preset. function blockEntityAnimationMode(presetType) { switch (presetType) { case 'animated': @@ -239,8 +307,26 @@ function blockEntityAnimationMode(presetType) { } } -// Block entities ignore movement/behavior/attributes; the host is always a -// static cuboid block. +function defaultRenderingSettings(presetType) { + return { + scale: 1, + shadowRadius: presetShadowRadius(presetType), + visibleBoundsWidth: 0, + visibleBoundsHeight: 0, + visibleBoundsOffset: [0, 0, 0] + }; +} + +function defaultAnimationSettings(mode) { + return { + mode: mode, + swingSpeed: 1, + walkSpeedMultiplier: 1, + idleStrength: 1, + gait: 'natural' + }; +} + function blockEntityPresetDefaults(presetType) { return { schemaVersion: SCHEMA_VERSION, @@ -251,12 +337,8 @@ function blockEntityPresetDefaults(presetType) { movement: {speed: 0, stepHeight: 0, gravity: false}, behavior: {mode: 'static', lookAtPlayers: false, randomStroll: false}, attributes: {maxHealth: 10, movementSpeed: 0, followRange: 16}, - rendering: {scale: 1, shadowRadius: 0.5}, - animation: { - mode: blockEntityAnimationMode(presetType), - swingSpeed: 1, - walkSpeedMultiplier: 1 - } + rendering: defaultRenderingSettings('cuboid_still'), + animation: defaultAnimationSettings(blockEntityAnimationMode(presetType)) }; } @@ -266,8 +348,7 @@ function presetDefaults(presetType, modelType) { } const move = movementType(presetType); - const ground = move === 'ground'; - const speed = ground ? 0.22 : 0; + const movement = movementDefaults(presetType, move); const mode = behaviorMode(presetType); return { @@ -281,22 +362,22 @@ function presetDefaults(presetType, modelType) { }, dimensions: presetDimensions(presetType), movement: { - speed: speed, - stepHeight: ground ? 0.6 : 0, - gravity: presetType === 'static' || ground + speed: movement.speed, + stepHeight: movement.stepHeight, + gravity: movement.gravity }, behavior: { mode: mode, lookAtPlayers: mode === 'idle_only' || mode === 'ambient', - randomStroll: ground && mode === 'ambient' + randomStroll: wandersByMovement(move) && mode === 'ambient' + }, + attributes: { + maxHealth: 10, + movementSpeed: movement.speed, + followRange: 16 }, - attributes: {maxHealth: 10, movementSpeed: speed, followRange: 16}, - rendering: {scale: 1, shadowRadius: presetShadowRadius(presetType)}, - animation: { - mode: animationMode(presetType), - swingSpeed: 1, - walkSpeedMultiplier: 1 - } + rendering: defaultRenderingSettings(presetType), + animation: defaultAnimationSettings(animationMode(presetType)) }; } @@ -304,14 +385,21 @@ module.exports = { SCHEMA_VERSION, MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY, + MODEL_TYPES, + BODY_TYPES, + MOVEMENT_TYPES, + BEHAVIOR_MODES, PRESET_TYPES, BLOCK_ENTITY_PRESET_TYPES, + ANIMATION_MODES, + GAIT_TYPES, SELECTABLE_PRESET_TYPES, isStablePreset, isCustom, bodyType, behaviorModeFor, movementDefaults, + wandersByMovement, animationMode, presetDimensions, presetShadowRadius, diff --git a/plugins/easy_model_entities/src/model/templates.js b/plugins/easy_model_entities/src/model/templates.js index 853b6e53..d642de68 100644 --- a/plugins/easy_model_entities/src/model/templates.js +++ b/plugins/easy_model_entities/src/model/templates.js @@ -23,9 +23,42 @@ const { MODEL_TYPE_ENTITY, presetDefaults } = require('./presetTypes'); +const {getDefaultVersionId} = require('./versionMatrix'); const DEFAULT_PRESET = 'statue'; +const MODEL_SETTING_KEYS = [ + 'schemaVersion', + 'modelType', + 'presetType', + 'namespace', + 'profileId', + 'targetVersion', + 'customize', + 'host', + 'dimensions', + 'movement', + 'behavior', + 'attributes', + 'rendering', + 'animation' +]; + +function pickModelSettings(settings) { + if (!settings || typeof settings !== 'object') { + return settings; + } + + const picked = {}; + MODEL_SETTING_KEYS.forEach((key) => { + if (settings[key] !== undefined) { + picked[key] = settings[key]; + } + }); + + return picked; +} + function deepMerge(target, source) { const result = Array.isArray(target) ? target.slice() : ({ ...target @@ -48,13 +81,10 @@ function getDefaults() { return deepMerge(presetDefaults(DEFAULT_PRESET), { namespace: 'example_org', profileId: 'entity', - targetVersion: '1.20.1' + targetVersion: getDefaultVersionId() }); } -// Full settings object for a preset type, including the identity fields. The -// model type disambiguates presets that exist for both entities and block -// entities (e.g. "static"). function applyTemplate(presetType, modelType) { return deepMerge(getDefaults(), presetDefaults(presetType, modelType || MODEL_TYPE_ENTITY)); @@ -66,5 +96,6 @@ module.exports = { SELECTABLE_PRESET_TYPES, getDefaults, applyTemplate, - deepMerge + deepMerge, + pickModelSettings }; diff --git a/plugins/easy_model_entities/src/model/versionMatrix.js b/plugins/easy_model_entities/src/model/versionMatrix.js index c1456920..4882f2a9 100644 --- a/plugins/easy_model_entities/src/model/versionMatrix.js +++ b/plugins/easy_model_entities/src/model/versionMatrix.js @@ -23,30 +23,30 @@ const VERSIONS = [ { id: '1.20.1', label: 'Minecraft 1.20.1', - dataFormat: 15, - resourceFormat: 15, + data: {packFormat: 15}, + resource: {packFormat: 15}, enabled: true }, { id: '1.21.1', label: 'Minecraft 1.21.1', - dataFormat: 48, - resourceFormat: 34, - enabled: false + data: {packFormat: 48}, + resource: {packFormat: 34}, + enabled: true }, { id: '1.21.11', label: 'Minecraft 1.21.11', - dataFormat: null, - resourceFormat: null, - enabled: false + data: {packFormat: 94, minFormat: [94, 1], maxFormat: [94, 1]}, + resource: {packFormat: 75, minFormat: [75, 0], maxFormat: [75, 0]}, + enabled: true }, { id: '26.1.2', label: 'Minecraft 26.1.2', - dataFormat: null, - resourceFormat: null, - enabled: false + data: {packFormat: 101, minFormat: [101, 1], maxFormat: [101, 1]}, + resource: {packFormat: 84, minFormat: [84, 0], maxFormat: [84, 0]}, + enabled: true } ]; @@ -68,12 +68,12 @@ function getDefaultVersionId() { function getPackFormats(id) { const version = getVersion(id); - if (!version?.enabled) { + if (!version?.enabled || !version.data || !version.resource) { return null; } return { - dataFormat: version.dataFormat, - resourceFormat: version.resourceFormat + data: version.data, + resource: version.resource }; } diff --git a/plugins/easy_model_entities/src/package-lock.json b/plugins/easy_model_entities/src/package-lock.json index 1f69970a..650438b4 100644 --- a/plugins/easy_model_entities/src/package-lock.json +++ b/plugins/easy_model_entities/src/package-lock.json @@ -8,11 +8,11 @@ "name": "easy_model_entities", "version": "0.1.0", "devDependencies": { - "blockbench-types": "^4.9.0", - "copy-webpack-plugin": "^12.0.2", - "jest": "^29.7.0", - "webpack": "^5.80.0", - "webpack-cli": "^5.1.4" + "blockbench-types": "5.1.0", + "copy-webpack-plugin": "14.0.0", + "jest": "30.4.2", + "webpack": "5.107.2", + "webpack-cli": "7.0.3" } }, "node_modules/@babel/code-frame": { @@ -71,6 +71,16 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", @@ -105,6 +115,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-globals": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", @@ -512,35 +532,96 @@ "license": "MIT" }, "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-1.1.0.tgz", + "integrity": "sha512-Xc3VhU02wqZ1HvHRJUwL09HkZSTvidqY5Ya0NXBSYOxAp+Ln9dcJr9fySI+CkONzP3PekQo9WdzCv0PGER/mOA==", "dev": true, "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=14.17.0" + } + }, + "node_modules/@electron-internal/extract-zip": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.4.tgz", + "integrity": "sha512-Zr1Vs7E9tpCNhZHDAbFVXc2gEVCG9RqPDjrno5+bdgB6LRAuvgyMHJut4NCVyYwtAieapMzc3fiQ3CSTi75ARg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=22.12.0" } }, "node_modules/@electron/get": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", - "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.0.0.tgz", + "integrity": "sha512-pjoBpru1KdEtcExBnuHAP1cAc/5faoedw0hzJkL3o4/IJp7HNF1+fbrdxT3gMYRX2oJfvnA/WXeCTVQpYYxyJA==", "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.1", - "env-paths": "^2.2.0", - "fs-extra": "^8.1.0", - "got": "^11.8.5", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", "progress": "^2.0.3", - "semver": "^6.2.0", + "semver": "^7.6.3", "sumchecker": "^3.0.1" }, "engines": { - "node": ">=12" + "node": ">=22.12.0" }, "optionalDependencies": { - "global-agent": "^3.0.0" + "undici": "^7.24.4" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" } }, "node_modules/@istanbuljs/load-nyc-config": { @@ -571,71 +652,61 @@ } }, "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", + "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.4.1", "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/console/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz", + "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.4.1", + "@jest/pattern": "30.4.0", + "@jest/reporters": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.4.1", + "jest-config": "30.4.2", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-resolve-dependencies": "30.4.2", + "jest-runner": "30.4.2", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "jest-watcher": "30.4.1", + "pretty-format": "30.4.1", + "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -646,127 +717,150 @@ } } }, - "node_modules/@jest/core/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/@jest/diff-sequences": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", + "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", + "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-mock": "^29.7.0" + "jest-mock": "30.4.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "expect": "30.4.1", + "jest-snapshot": "30.4.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", + "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "@jest/get-type": "30.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", + "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", + "@jest/types": "30.4.1", + "@sinonjs/fake-timers": "^15.4.0", "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", + "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/types": "30.4.1", + "jest-mock": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "@types/node": "*", + "jest-regex-util": "30.4.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", + "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", + "@jest/console": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", + "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", + "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", + "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -777,139 +871,125 @@ } } }, - "node_modules/@jest/reporters/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/@jest/schemas": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "node_modules/@jest/snapshot-utils": { + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", + "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@jest/types": "30.4.1", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", + "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "@jest/console": "30.4.1", + "@jest/types": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", + "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", + "@jest/test-result": "30.4.1", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.4.1", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", + "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", + "@babel/core": "^7.27.4", + "@jest/types": "30.4.1", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", + "pirates": "^4.0.7", "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "write-file-atomic": "^5.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jridgewell/gen-mapping": { @@ -973,76 +1053,55 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@tybys/wasm-util": "^0.10.3" }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, + "optional": true, "engines": { - "node": ">= 8" + "node": ">=14" } }, - "node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "node_modules/@pkgr/core": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", + "integrity": "sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": "^14.18.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "url": "https://opencollective.com/pkgr" } }, - "node_modules/@sindresorhus/merge-streams": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "node_modules/@sinclair/typebox": { + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "MIT" }, "node_modules/@sinonjs/commons": { "version": "3.0.1", @@ -1055,26 +1114,24 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "@sinonjs/commons": "^3.0.1" } }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" + "tslib": "^2.4.0" } }, "node_modules/@types/babel__core": { @@ -1122,19 +1179,6 @@ "@babel/types": "^7.28.2" } }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, "node_modules/@types/dompurify": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", @@ -1152,23 +1196,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -1213,24 +1240,14 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/node": { - "version": "20.19.43", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", - "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.18.0" } }, "node_modules/@types/prismjs": { @@ -1240,16 +1257,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/sizzle": { "version": "2.3.10", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz", @@ -1302,68 +1309,377 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "node_modules/@ungap/structured-clone": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", + "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } + "license": "ISC" }, - "node_modules/@vue/compiler-sfc": { - "version": "2.7.14", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz", - "integrity": "sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==", + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], "dev": true, - "dependencies": { - "@babel/parser": "^7.18.4", - "postcss": "^8.4.14", - "source-map": "^0.6.1" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@vue/compiler-sfc": { + "version": "2.7.14", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.14.tgz", + "integrity": "sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.18.4", + "postcss": "^8.4.14", + "source-map": "^0.6.1" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } @@ -1485,53 +1801,6 @@ "@xtuc/long": "4.2.2" } }, - "node_modules/@webpack-cli/configtest": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", - "integrity": "sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-2.0.2.tgz", - "integrity": "sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-2.0.5.tgz", - "integrity": "sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -1546,49 +1815,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/ajv-formats": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", @@ -1607,19 +1833,30 @@ } } }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "peerDependencies": { - "ajv": "^8.8.2" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -1637,13 +1874,16 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { @@ -1676,6 +1916,19 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -1687,85 +1940,58 @@ } }, "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", + "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", + "@jest/transform": "30.4.1", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.4.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-jest/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", "test-exclude": "^6.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", + "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" + "@types/babel__core": "^7.20.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/babel-preset-current-node-syntax": { @@ -1796,20 +2022,20 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", + "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" + "babel-plugin-jest-hoist": "30.4.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "node_modules/balanced-match": { @@ -1820,9 +2046,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.37", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.37.tgz", - "integrity": "sha512-girxaJ7WZssDOFhzCGZTDKoTa1gk6A1TbflaYTpykLJ4UU9Fz9kx1aREM8JCuoVHbL8X8T/mJg7w2oYSq72Oig==", + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1833,9 +2059,9 @@ } }, "node_modules/blockbench-types": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/blockbench-types/-/blockbench-types-4.12.2.tgz", - "integrity": "sha512-OkqlV5RO5PAV7fkix9MfVZD1bomJpw33q7vUwA0Otekd3N2Cf+f0im0diHjsZiFACmvApMfa60jajNlD6Cqj3A==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/blockbench-types/-/blockbench-types-5.1.0.tgz", + "integrity": "sha512-JLMHJZ+2J7aFQwSacfW4N/rvd56JRqvaBsHNH2R6sjyxkwKPuNPKYIeav/WtRXnq3dmlsEoH3rtg09s7yhe3sg==", "dev": true, "dependencies": { "@babel/types": "^7.20.7", @@ -1845,51 +2071,28 @@ "@types/three": "^0.129.2", "@types/tinycolor2": "^1.4.6", "dompurify": "^3.0.1", - "electron": "^33.3.1", + "electron": "^40.8.0", "prismjs": "^1.29.0", "tinycolor2": "^1.6.0", - "typescript": "^4.9.5", + "typescript": "^5.8.3", "vue": "2.7.14", "wintersky": "^1.3.0" } }, - "node_modules/boolean": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", - "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" + "balanced-match": "^1.0.0" } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", "dev": true, "funding": [ { @@ -1907,10 +2110,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -1930,16 +2133,6 @@ "node-int64": "^0.4.0" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -1947,35 +2140,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.6.0" - } - }, - "node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2051,45 +2215,108 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=6.0" + "node": ">=6.0" + } + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, "engines": { "node": ">=8" } }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/clone-deep": { @@ -2107,19 +2334,6 @@ "node": ">=6" } }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -2158,13 +2372,6 @@ "dev": true, "license": "MIT" }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -2187,21 +2394,20 @@ "license": "MIT" }, "node_modules/copy-webpack-plugin": { - "version": "12.0.2", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", - "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-14.0.0.tgz", + "integrity": "sha512-3JLW90aBGeaTLpM7mYQKpnVdgsUZRExY55giiZgLuX/xTQRUs1dOCwbBnWnvY6Q6rfZoXMNwzOQJCSZPppfqXA==", "dev": true, "license": "MIT", "dependencies": { - "fast-glob": "^3.3.2", "glob-parent": "^6.0.1", - "globby": "^14.0.0", "normalize-path": "^3.0.0", "schema-utils": "^4.2.0", - "serialize-javascript": "^6.0.2" + "serialize-javascript": "^7.0.3", + "tinyglobby": "^0.2.12" }, "engines": { - "node": ">= 18.12.0" + "node": ">= 20.9.0" }, "funding": { "type": "opencollective", @@ -2211,28 +2417,6 @@ "webpack": "^5.1.0" } }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2273,35 +2457,6 @@ } } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dedent": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", @@ -2327,54 +2482,6 @@ "node": ">=0.10.0" } }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -2385,57 +2492,46 @@ "node": ">=8" } }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true, - "license": "MIT", - "optional": true - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/dompurify": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.10.tgz", - "integrity": "sha512-0xzNv0e7oYC6yyuOGZIABPM4qtg3QxLFniDNPP4ZP90wR8Yq3zgwpRbrNiT4N3IKqDbbYFEJLV+JWEs19aZ//w==", + "version": "3.4.11", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", + "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", "dev": true, "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/electron": { - "version": "33.4.11", - "resolved": "https://registry.npmjs.org/electron/-/electron-33.4.11.tgz", - "integrity": "sha512-xmdAs5QWRkInC7TpXGNvzo/7exojubk+72jn1oJL7keNeIlw7xNglf8TGtJtkR4rWC5FJq0oXiIXPS9BcK2Irg==", + "version": "40.10.5", + "resolved": "https://registry.npmjs.org/electron/-/electron-40.10.5.tgz", + "integrity": "sha512-VzTIvwOYXZZufT9B83GDQogR1TFqREygRYhm0LE++QhGPjvBeg+W7siOP9K5+9rHMUnRuCX4YU/0ivLekN/UZQ==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "@electron/get": "^2.0.0", - "@types/node": "^20.9.0", - "extract-zip": "^2.0.1" + "@electron-internal/extract-zip": "^1.0.1", + "@electron/get": "^5.0.0", + "@types/node": "^24.9.0" }, "bin": { "electron": "cli.js" }, "engines": { - "node": ">= 12.20.55" + "node": ">= 22.12.0" } }, "node_modules/electron-to-chromium": { - "version": "1.5.372", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.372.tgz", - "integrity": "sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==", + "version": "1.5.378", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.378.tgz", + "integrity": "sha512-VinvOAuuPmdD1guEgGv5f2Qp7/vlfqOrUOMYNnOD4wj3pit8kRsQHzfIf6teyUGWo15Tg5+bOJaRunvyltpVWQ==", "dev": true, "license": "ISC" }, @@ -2453,22 +2549,12 @@ } }, "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "license": "MIT" }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/enhanced-resolve": { "version": "5.24.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.0.tgz", @@ -2484,13 +2570,16 @@ } }, "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/envinfo": { @@ -2516,17 +2605,6 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", @@ -2544,14 +2622,6 @@ "dev": true, "license": "MIT" }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true, - "license": "MIT", - "optional": true - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2563,17 +2633,13 @@ } }, "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, "license": "MIT", - "optional": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/eslint-scope": { @@ -2671,64 +2737,39 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/execa/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "license": "ISC" }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" + "@jest/expect-utils": "30.4.1", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" }, "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/fast-deep-equal": { @@ -2738,36 +2779,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2788,61 +2799,18 @@ { "type": "opencollective", "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } + } + ], + "license": "BSD-3-Clause" }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" + "bser": "2.1.1" } }, "node_modules/find-up": { @@ -2869,19 +2837,21 @@ "flat": "cli.js" } }, - "node_modules/fs-extra": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", - "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/fs.realpath": { @@ -2947,38 +2917,35 @@ } }, "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3004,118 +2971,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/global-agent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", - "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "es6-error": "^4.1.1", - "matcher": "^3.0.0", - "roarr": "^2.15.3", - "semver": "^7.3.2", - "serialize-error": "^7.0.1" - }, - "engines": { - "node": ">=10.0" - } - }, - "node_modules/global-agent/node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/globby": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", - "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/merge-streams": "^2.1.0", - "fast-glob": "^3.3.3", - "ignore": "^7.0.3", - "path-type": "^6.0.0", - "slash": "^5.1.0", - "unicorn-magic": "^0.3.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3133,20 +2988,6 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -3167,27 +3008,6 @@ "dev": true, "license": "MIT" }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -3198,16 +3018,6 @@ "node": ">=10.17.0" } }, - "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -3333,16 +3143,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -3413,19 +3213,6 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -3442,15 +3229,15 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "istanbul-lib-coverage": "^3.0.0" }, "engines": { "node": ">=10" @@ -3470,23 +3257,39 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz", + "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" + "@jest/core": "30.4.2", + "@jest/types": "30.4.1", + "import-local": "^3.2.0", + "jest-cli": "30.4.2" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -3498,86 +3301,107 @@ } }, "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", + "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", + "execa": "^5.1.1", + "jest-util": "30.4.1", "p-limit": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-changed-files/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", + "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", + "pretty-format": "30.4.1", + "pure-rand": "^7.0.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-circus/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/jest-circus/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", + "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" + "@jest/core": "30.4.2", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "yargs": "^17.7.2" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -3589,235 +3413,211 @@ } }, "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", + "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.4.0", + "@jest/test-sequencer": "30.4.1", + "@jest/types": "30.4.1", + "babel-jest": "30.4.1", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-circus": "30.4.2", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-runner": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", + "pretty-format": "30.4.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "@types/node": "*", + "esbuild-register": ">=3.4.0", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "esbuild-register": { + "optional": true + }, "ts-node": { "optional": true } } }, - "node_modules/jest-config/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/diff-sequences": "30.4.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.4.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", + "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", "dev": true, "license": "MIT", "dependencies": { - "detect-newline": "^3.0.0" + "detect-newline": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", + "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "@jest/types": "30.4.1", + "chalk": "^4.1.2", + "jest-util": "30.4.1", + "pretty-format": "30.4.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", + "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-mock": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", + "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", + "@jest/types": "30.4.1", "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", + "picomatch": "^4.0.3", "walker": "^1.0.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "optionalDependencies": { - "fsevents": "^2.3.2" + "fsevents": "^2.3.3" } }, "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", + "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "pretty-format": "30.4.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", + "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.4.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-util": "30.4.1", + "picomatch": "^4.0.3", + "pretty-format": "30.4.1", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-util": "^29.7.0" + "jest-util": "30.4.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-pnp-resolver": { @@ -3839,216 +3639,211 @@ } }, "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", "dev": true, "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", + "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.4.1", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz", + "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" + "jest-regex-util": "30.4.0", + "jest-snapshot": "30.4.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz", + "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.4.1", + "@jest/environment": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-haste-map": "30.4.1", + "jest-leak-detector": "30.4.1", + "jest-message-util": "30.4.1", + "jest-resolve": "30.4.1", + "jest-runtime": "30.4.2", + "jest-util": "30.4.1", + "jest-watcher": "30.4.1", + "jest-worker": "30.4.1", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", + "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/globals": "30.4.1", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", + "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.4.1", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.4.1", + "graceful-fs": "^4.2.11", + "jest-diff": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "pretty-format": "30.4.1", + "semver": "^7.7.2", + "synckit": "^0.11.8" }, "engines": { - "node": ">=10" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.4.1", "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", + "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", + "@jest/get-type": "30.1.0", + "@jest/types": "30.4.1", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "^29.7.0" + "pretty-format": "30.4.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-validate/node_modules/camelcase": { @@ -4065,39 +3860,40 @@ } }, "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", + "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" + "jest-util": "30.4.1", + "string-length": "^4.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", + "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-util": "^29.7.0", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.4.1", "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "supports-color": "^8.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker/node_modules/supports-color": { @@ -4150,13 +3946,6 @@ "node": ">=6" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -4164,21 +3953,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true, - "license": "ISC", - "optional": true - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -4192,26 +3966,6 @@ "node": ">=6" } }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -4222,16 +3976,6 @@ "node": ">=0.10.0" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -4276,16 +4020,6 @@ "node": ">=8" } }, - "node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4312,19 +4046,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", - "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -4335,20 +4056,6 @@ "tmpl": "1.0.5" } }, - "node_modules/matcher": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", - "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "escape-string-regexp": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -4356,40 +4063,6 @@ "dev": true, "license": "MIT" }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -4400,27 +4073,30 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.2" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" } }, "node_modules/molangjs": { @@ -4438,9 +4114,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", "dev": true, "funding": [ { @@ -4456,6 +4132,22 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4478,9 +4170,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", - "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "version": "2.0.49", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.49.tgz", + "integrity": "sha512-f06bl1D+8ZDkn2oOQQKAh5/otFWqVnM1Q5oerA8Pex7UfT66Tx4IPHIqVVFKqFT3FUtaDstdgkM7yT7JWhqxfw==", "dev": true, "license": "MIT", "engines": { @@ -4497,19 +4189,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -4523,17 +4202,6 @@ "node": ">=8" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -4560,27 +4228,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "p-try": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4599,22 +4257,6 @@ "node": ">=8" } }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -4625,6 +4267,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -4681,25 +4330,29 @@ "dev": true, "license": "MIT" }, - "node_modules/path-type": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", - "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, "engines": { - "node": ">=18" + "node": ">=16 || 14 >=14.18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, - "license": "MIT" + "license": "ISC" }, "node_modules/picocolors": { "version": "1.1.1", @@ -4709,13 +4362,13 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -4774,18 +4427,19 @@ } }, "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@jest/schemas": "30.4.1", + "ansi-styles": "^5.2.0", + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -4821,99 +4475,39 @@ "node": ">=0.4.0" } }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true, "funding": [ { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" }, - "node_modules/react-is": { + "node_modules/react-is-18": { + "name": "react-is", "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, "license": "MIT" }, + "node_modules/react-is-19": { + "name": "react-is", + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==", + "dev": true, + "license": "MIT" + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -4969,13 +4563,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true, - "license": "MIT" - }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -4999,112 +4586,6 @@ "node": ">=8" } }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lowercase-keys": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/roarr": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", - "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "dependencies": { - "boolean": "^3.0.1", - "detect-node": "^2.0.4", - "globalthis": "^1.0.1", - "json-stringify-safe": "^5.0.1", - "semver-compare": "^1.0.0", - "sprintf-js": "^1.1.2" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/roarr/node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", @@ -5125,63 +4606,64 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/semver-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "node_modules/schema-utils/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "dev": true, "license": "MIT", - "optional": true + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } }, - "node_modules/serialize-error": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "type-fest": "^0.13.1" - }, - "engines": { - "node": ">=10" + "fast-deep-equal": "^3.1.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "ajv": "^8.8.2" } }, - "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "license": "(MIT OR CC0-1.0)", - "optional": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.6.tgz", + "integrity": "sha512-ATTK5Q4gFVg0YDp1my2vqygyvhcklD/UV5GIlYHooGTn/NogJqIzpetkD6E5kmuVULqz/S9inUL25XcAgDRJQg==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/shallow-clone": { @@ -5221,30 +4703,26 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "MIT" + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/slash": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=8" } }, "node_modules/source-map": { @@ -5268,9 +4746,9 @@ } }, "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", "dependencies": { @@ -5298,16 +4776,6 @@ "node": ">=10" } }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -5322,7 +4790,49 @@ "node": ">=10" } }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", @@ -5337,15 +4847,72 @@ "node": ">=8" } }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5422,6 +4989,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/synckit": { + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.13.tgz", + "integrity": "sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.3.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/tapable": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", @@ -5547,15 +5130,17 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/terser/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "node_modules/terser/node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" } }, "node_modules/test-exclude": { @@ -5573,6 +5158,52 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/three": { "version": "0.134.0", "resolved": "https://registry.npmjs.org/three/-/three-0.134.0.tgz", @@ -5587,6 +5218,41 @@ "dev": true, "license": "MIT" }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -5594,18 +5260,13 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } + "license": "0BSD", + "optional": true }, "node_modules/type-detect": { "version": "4.0.8", @@ -5631,9 +5292,9 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5641,37 +5302,63 @@ "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20.18.1" } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, - "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "napi-postinstall": "^0.3.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" } }, "node_modules/update-browserslist-db": { @@ -5803,43 +5490,37 @@ } }, "node_modules/webpack-cli": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", - "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-7.0.3.tgz", + "integrity": "sha512-2E2C6A1e2El7791zQgTH7LPIuwLjRliow9OHS/qlJc9pwhZlCoL/uiwqd/1WSlXT83wJfmfDbkcqHXuXoPJZ3g==", "dev": true, "license": "MIT", "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.1", - "@webpack-cli/info": "^2.0.2", - "@webpack-cli/serve": "^2.0.5", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", + "@discoveryjs/json-ext": "^1.1.0", + "commander": "^14.0.3", + "cross-spawn": "^7.0.6", + "envinfo": "^7.14.0", "import-local": "^3.0.2", "interpret": "^3.1.1", "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" + "webpack-merge": "^6.0.1" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { - "node": ">=14.15.0" + "node": ">=20.9.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "webpack": "5.x.x" + "webpack": "^5.101.0", + "webpack-bundle-analyzer": "^4.0.0 || ^5.0.0", + "webpack-dev-server": "^5.0.0" }, "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, "webpack-bundle-analyzer": { "optional": true }, @@ -5849,28 +5530,28 @@ } }, "node_modules/webpack-cli/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, "license": "MIT", "engines": { - "node": ">=14" + "node": ">=20" } }, "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dev": true, "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", - "wildcard": "^2.0.0" + "wildcard": "^2.0.1" }, "engines": { - "node": ">=10.0.0" + "node": ">=18.0.0" } }, "node_modules/webpack-sources": { @@ -5883,6 +5564,42 @@ "node": ">=10.13.0" } }, + "node_modules/webpack/node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/webpack/node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5919,6 +5636,25 @@ } }, "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", @@ -5936,6 +5672,64 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -5944,17 +5738,17 @@ "license": "ISC" }, "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/y18n": { @@ -5975,9 +5769,9 @@ "license": "ISC" }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "dev": true, "license": "MIT", "dependencies": { @@ -6003,15 +5797,49 @@ "node": ">=12" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, "node_modules/yocto-queue": { diff --git a/plugins/easy_model_entities/src/package.json b/plugins/easy_model_entities/src/package.json index e4d98f37..99fbd589 100644 --- a/plugins/easy_model_entities/src/package.json +++ b/plugins/easy_model_entities/src/package.json @@ -10,11 +10,11 @@ "test": "jest" }, "devDependencies": { - "blockbench-types": "^4.9.0", - "copy-webpack-plugin": "^12.0.2", - "jest": "^29.7.0", - "webpack": "^5.80.0", - "webpack-cli": "^5.1.4" + "blockbench-types": "5.1.0", + "copy-webpack-plugin": "14.0.0", + "jest": "30.4.2", + "webpack": "5.107.2", + "webpack-cli": "7.0.3" }, "jest": { "testEnvironment": "node", diff --git a/plugins/easy_model_entities/src/resources/icon.png b/plugins/easy_model_entities/src/resources/icon.png index 7e7bf10a3ea50f305adc8969e8010b9b87a8b375..7c6f764e34a9cb76c939a648f57f4116c05dfe6e 100644 GIT binary patch literal 2597 zcmYk7c{CK<8^`YqV+#%05>gl%3^Gcxj1kS)8d7$tkVc5Iq&EgpqwG>rmSh>Bv9B?9 ziYS%QYnf0AgYX)=-}E}?_xqjm-0%H9&;2~l{pX%@$pp*O0(_Eu0003z?vyoqcm1v9`4({xbj9%8D==;gW`&Ut6o7pA|P$FsZ5)dXek-QrS2d zIl24~b?h7IU2jnLyK@hbaBYR{ot@F&iw%?C3x-Fc-uFx876@d&lrcmq+9Q~()xO!; zv-NK@im6J4#TY-NX^axW8c{tyak;sb`pH7q1&#Gba>y&`gebI3{Uo+^aA&@-O+ErW zgjil*_v`GosBQ2mIv!qiEa0`~#pg(Dd6jlq#gleJpF&lwOGjk!iuxpks+;@(bFTB( zbn2Hu+c!;l?@i-r1{%&tY^0*rRr$gBg{-&6_v&@65|pi?ktci<4^xV&zB+`I9XobM z?Q*29<87UWVW-Sy)AR;>YBkO@6uI|#7BzEBHZ0}oPZBH(8uBdO$O|>P+ z?&bMV94`N*sW9b+-q>(YW#)78B||l}!$Y6yURj+U>vD4y{rR_x^H$v8CIH-z@TUxI zJV)Ep$d1B>5*_VSv@(;bQa`_;A4opLJDGsJFK0o3KQgjOutuI;GG|(s8`aOx#LuRW zxQo*T>>M&jrxLksgTpk(N;S*HsK_E@g89b#rPtn%viT^R56rq+v0$xkc1$nurGMYo zYX`q#lN48NgJdgCrf*1|z;3;L!My$6ZJk~xoHX3o^cNx8Eoy&bUW74eV@C} ziiL9uNh;#4S#t{Qb_#9Urb#EGI_(h&Q7tjvDI2ad!8u;wQPJ3d>QOpmHkcP};*!|4 zG#5abb5E%c6&c>gca%iFSM3!R%6utt;5;I1hL)|moW9ta`<)KcJ?|@u3mGEL$}II# z$ZmRob!M;cg_mttuHQPzSF24PD~22#dfH;c8cC@yRe;VNiFs%c96oYl3-0zT!a>2z z@`GMX;4G6%DC-{xLv9ICyyOq$$-ZH*1lKJ9YVuY_0O^p(LOY~xqsS-7In%jo?4+fBNb4CR<_!6?JQ-pSBM~D!)ob0P8}DNT zM;l5+0~Ix)F?(3^XShh4*%BWmb$^lKby>&hdID763%4|;8ck4lhf4FE`?DJKXnCtX(pyaG&bFamB1^Q>*(K{pPZSt_m<++}EA_1UZhk$j^Do z6WS0w@s0Bdo*%?AZnkms`Qc3k86U$-5+^@GFog=-PKofWjB`_bH9Iw0dol2K?Di~h zp1EwdlfDF=B$!3b58DH8Q$hxbE8p-zOTe9JbMUSSFao{|hG+1C9uHn{^X6<^>P=GZ z8h*!u>;a}d@yy5{imk4-cLDtgN>sKi1#DolbOcYpfn7+L)|MQmIAb6l#)Ac}IU`HZ ziq)$abje=&w=!AGW7eCl6M2&Uu=+QVaTM9c4vx)@kxv8TCdi;Ya~v|5rMS}KCi_e^ zn*Qs9()a6y#v#|#9Mihc#|4!gmmmckkum{omW29-#!vco!JX|}Wa~6>hJBBF*%hY` z2w&^Ufi!N~{%eQUH`5-Q- zb{uJjB{o^qauj9VLi+aJ1A(3oQKies$di+YrA-PeaFvSS2P>JMTxerqpx^2v!rQN7 zbP#@Y9QRxN4hrpRkW(}Gs#!w^B}h7>U2pl?B{TY0&Pw9)3Z?M@AkPBxfAw59{n(x&3aXH!31{{+Ug!Y){ z_~&Ya55+5Sh8Am^A{bkvsJ!ACnVK?^E$df|cik;%_}{iM&}Hp!&wkus;3%WkX9m#P zXtLkmUqz~?1Dt$_46#NR6@b1pZOGHi*huS_@9_3Q=QFFvlpjBUng?k*tE>Z-5h@L&f2GM|p@n`Mk4I=t&gbtq3qEueT9q3&qyW=gncd?%aJ)3@e?cA4ChS$tFe;u_IGhPyH6i&*mi{oArZVJ34dt zAN7#~WJ*6BaFBeUTXKwL;(qPv%4@EhIX-CSxyv+BYbbcAkAq9myc8v|NH-eED+yGf z&WU)?w4QcOt&&PSAn5Oc1#&-r+@njuJ3Q&%y#dBYQ@x4#mJbXXb4#zR=-bly&e?1o zC!s{uZ_Q8+D}wC!H7S$iZo1!RBX}4LJrb5%y4-jL)U7-65#oSq5$DCpLIf8R3Ic2R zV!tnbf42t!iJAux%<9Ba0CSya2qx}9>@+11GOnb1sHQtI`iud`g$a;#JEj3F2=fRC zuZRF$mlBv91qdu$em)NNE`(u@LPCt86_(VM7q2pe_@4yd5lVqj`+3arlrZthmSx$k z$L}7Ty~3%&m^%IHd<^tdeHzj~xxATn|IR_*)wu;3)4X$JIk{6r&r& za0U;Vc!_h06w}fhs!#{Y^+W~Kbu{~2jhjcA#x!vI*vfd)GXXF*WM^+HLRd1|G*k-7EVmc57Ib$~3=jmKEdq!pw4Q2(wh?jsbl7t>4eE#;CZn#VmRjk zRYpaCcCpeS)+%^^dB?DwS7$Zvn(i&cO2ClBz(mZwUv Ht`YwM?nSh} literal 5255 zcmV;26nN{2P)_{r~^}Yp5$)nkZ3`Cu*lE03Rs-{rzsLELoZ;x7knp{r+vKD_xu@TbwBV|Npt# zQC^)XZK^9(mMH)J{|_}K{QUeMRUz;8_ezT=02(U(|Ni>@{`vd-1t%y+hbLs9DLQ*5 z^!fQzlPBf!^AtQM01PK?tt?uXDF9(=PmU-6YIF!LCp2><|N8pO=;{J?fdFuN;o>_xQcv;s9lD02C-0L?`O>^=hatICv%~U?t!0@bmrtf4jW~e1!m4 zVrZl)V4^8Nbto}wCIAy1{`K|N?CincR)D!QWTh$+Kq47TAOHOSn9k97x4jWBC(z^M zIGLbDfG0hFCIA8@000o``1{4;HZgUsFX-#+QJP-kQhDMW?S?rK$i?T)f>;pUXr58X+4`Alml% zsO9Ro<>-LJ$RL!V8jqJAhmQe-j>qFyk-O z@AFQvwmYJ!MWClCo~EhIPE3C)R-7pta0Gkp*RhjJbJRZ*$AwY;dhv zoVP||kvFE#F(zy!B-Gc##;lB;oPV5(`@R}{xdE?}M*fkHwGWHCcRQj(Mr_K*#g77%136p__}$P@|+ zxDY{cwC=6dR$E7FtKCz(_uhN&z0Q62y}YcpU%#*IU&Fh(FFC(^&OP_!=7JNC$LI4H zacw>bx9#t6_emdVk+$`FNzeZQaQsHgw-VWaSo8K^5NG560B}aRx7mis*1V&8j$!{j z0ADzW{S?}o2We@FtI>;ITP18<8dJy6aS|AV#x$EBa zu`#V-j{K%?_P?-LS6*JOc<@zIAwp*3T$s>3FFyLzH8vrF?xOW^MGgQ$DazkzjLXpO z9+x2`zG=gwVGZZqv*%T8+T{PheL=_p;OiHU-@^gS#`p{3>JqKX6@=ZXWlvw>300+_v<>?V-7wDg+PSY!Ys^E{}Jd+y4}w>EQq%y*8uB8USNA7&UQ| zk9Xxq@MPrP%13WYy6w$3pRb=j^UZ@WBNaD3aG!j$CE&gN%6qIz1;wNkwe#p!Idm~& z&x>uT%g`9oThhSx(0JD{LY|&tF#w)_zP|s~S+w3b78<>&V%3AMY=(D`bSb>B|7o^_ z5`aWPo<-=lu9x94NWgx6Z=5E>SlXRW!5f9bv^C>*k5|e7FzeCh>sR*AVgUWK`p?jQ zeDLL~a>U1g)C+IkLC*lO>=Yzx?Q7Cg6rWGwD1b59&9#%-vAEs$R*|USK0ZHhyZ*rA z%K&hpe&sA%O?O0BMEhU;@y#ZLB=msAsPN!>7v6M2P89X->qoW4iS*d<9pPQtj127p zdg!c#6yIq$G4;Spvdt@w+XGgtxb@6Kx37w>P<*s@&ijX?ZsGmNdM8$c`f^!KyoFCtp8=UH9UGnbO2 zb$ajki|5WgeK3Xx9!M$uZeyJB1fKvxwq~?;>4#W=G-z$t-7lx6hWJu6VEXuNw>|tY z%!3VJ#fm-Cx9gs}lK}49f9~8v2Tdksp?S(Cv`2T7h@-?lLzjN^$dfH=aP>KG_kp!0 z%ZfVp;b)&sx&h2%tFz$$tjnihfqZ^iZr1I*w0hT2uMFSsl;fdJ4KPalM3Z~nj` zYk_I`rcdIQlwUhFHPu_3hNP5b@!Rtj44V-i`ilOBV0V$5$i>agZD8J&momkZFBapL z6@Wxto@!V}MjWEAT)V?)gd$-H`06`0<`=EFY{O-@PM>!bxw(6QeGGVx0L$Q{GJkg^m5_z=Q^ZEv{jqLp(S0=&H37`eFkfd}-fz+7P= zgb0KIc-S?k+YaZ`%@D&n0;ty0FAb_`GfeH;62t(0`EV8;oDD0cuL{U2C}-+p~^0Uc2_D? zo6o=7umN&1t02>P23CMO2T-yA^wjZZk01a2i+jKWaERi`WfHs!hzF6^62L^UP{>ZM zg!1GiF2CNe?$Dm;rC9}F*x8vpum>(60+_duNqY%j!rLr?=wJZigIHto#K^}9pt^|z z(6tf8tX=op!%GWhgJBNfZUOKi9*{ZQd=r?p-M$kKghJeB3bNdalSbv}zNO9}Fiwli z0T>6lxXdR2tIdENKoY_Ng6sg0#3z?}a{$@{4@VhGg$c)$%xEEYQg@Iy2VAa^S}y#5I4!Rm@Hxdb6}z2 zYg-RbepstHateI7hW0>xOb4UZxsyOeVf#^w-M|9?Sa0QR0c0Jp9&{1FJ0%p9?Z$VH z9^mmd({r%b-yZ|iEC4GT0P`IIY`5P+^Ez0xmh?VZO}|`z6l#>XxIGB~w8?pDvfvc) zQStHf=DWc{3*{ymUmXC*O11$sl9%4n%H!QsjOQR5$3&oD06r>#q9a@6@+SbU$+(rM zVXA4-1fL%h10kv*z+38sLh!&D0K@}kk`ol+Sst`t4nDFjJ@x?bfCVhSmjFhYJ@;z> zxM>cAhym!%()OLzt_3V$o+Q&K3Y3#O-G0Ku=#x3r6`A4d}yJgaL~c*nWo#w(mV-UwE(?aHS)F&6n;( z?QXgyUe09C{gJg*2vO86>tP)LV77C%Psie(oN0kb=F>dbC8ro0E&2>LI7pRKVIeIswRN>QQ(0s!0y~cGC9`P z6av;-UZ(phN1B}S*l(hh&CW@)X@PZI<>Cd3ZBT|FgGAxUI@JevT8GFU06G+z{=|+F z9;($-bWKm}o+zqGLo0w6dbe_f7ppU4dtx^1BX)s3H-8~|*cLPOnc=|}GGIq8cK z(-(AjWZ96fNi0t5@do4G-ZN(kRQ|Bm{Iak_%IpNy#s@zA_(v169Bx;%J#dFT!1|z)%9AGE zZOG81pOgvbbo&mSj*Eovw3ONNv{UU6A}~KYGaw*5AztOL^SL}Bp)+|PFz}KTI7fQ! z=T{IS_8bCA4!3HKYVg;5p75~~CST-KRF|PWy@)-eLp@1LF~Hk-@9hby@~DK)&dvk? z1P+u9_=Woo_(VrXKkzE9F!pK6Glf*|j2qLeTNWWh6y7?v`1;}_ve;clZEtrP9pA#9 zr46trI(tl~Dp&cW4CdtI3?_tzb|#mV4P4k47IxM74pn*i1FvpoedYj2-t&Y(YZ%EV zTP#q|y)+;uf?7M$XPBB8!YjG^;jpmt+u~I!K?hg|Ey?LfNJuGDhKmBu1K_HLfZKJ^ z(a#*T10;@Ww39_?6jy8)jR1f=3##3*Ta&SFIPaP*FLcf;FZcK9Tyn`JOO`Cj$+;wX zu%lzKI?H)xL&H^|xpV91etY$%2kiiB(~Z3~cTkWqEMQA-A^?aUdkhWl&}wV9FTc`1 zUa>l55bVEW&xF@OZ0%P&q@tiIumGiR=v z;q8noReUr%G&7*8t@lo{Bfe`Yy>wS|Y{}?Iq8Wf^1MX62IgCF!a3EOiZU$rnVD;*Y zQ_j9IJ-0p6*%rWVwDrBcih@QYQrK=an-Q6vi0fd4F z7e8`wj@p|D04fXsSpud?X@FD;5#PdtzLT{qfG00*Vk+&NGCwyD0#KU)It+MZ@T~Jp zGa%Qh7U74!t#k{I(V`MMHNgWWfc)5eCbi~>;CzJuTr52R4Z6={0AXPO0GR;?dLkYfOr zy$2vE07^MP3;wB}%9pc%X1YfT9(Z{D9l*u|)x!10AD*SeZQB3AWs zfGyQxb)KGk>Vz!5(zu10Th>C0qPt8a`;~O z%&&N6A!3gPSpX=)6g#3BGv1rVJJEz!ZP9`d345>c6as+BH2$R00^mmjmiW+s)q`cO zu72AT#!xpu@5x3#OW1R-%mNpp}Sf>vQf{uU}CFE1kEnv-W;{rr?b zffxlVmC2wN5`1V6QU$$nBHm=&3yDAqR_$T~RMXy@5F=|ys#U>{9#F8TbG=w}v zEjx5w>(=$_)7Qg~y7aofzSgaMb#+FAp|92Ox_>;#pHoznLZA>pZ`AuRdV7GwKUn~b zkmrf;1OG$t;88fUJf+Z(B1Ctwx(P`o626rCZonO>nE@P?yF%W;gO&N)&!6gV0QMlq z{V~iNJwfDZ9|87?@BVW@%s&LM4*eAb8{zt~VcbK4@^`WW@c&(a(;qSPw { + test('schema version matches the committed mod contract', () => { + expect(SCHEMA_VERSION).toBe(contract.schema_version); + }); + + test('enum constants match the committed mod contract', () => { + expect(sorted(MODEL_TYPES)).toEqual(sorted(contract.enums.model_types)); + expect(sorted(BODY_TYPES)).toEqual(sorted(contract.enums.body_types)); + expect(sorted(MOVEMENT_TYPES)).toEqual( + sorted(contract.enums.movement_types)); + expect(sorted(BEHAVIOR_MODES)).toEqual( + sorted(contract.enums.behavior_modes)); + expect(sorted(PRESET_TYPES)).toEqual(sorted(contract.enums.preset_types)); + expect(sorted(BLOCK_ENTITY_PRESET_TYPES)).toEqual( + sorted(contract.enums.block_entity_preset_types)); + expect(sorted(ANIMATION_MODES)).toEqual( + sorted(contract.enums.animation_modes)); + expect(sorted(GAIT_TYPES)).toEqual(sorted(contract.enums.gaits)); + }); + + test('validator budgets match the committed mod contract', () => { + const budgets = Validator.BUDGETS; + expect(budgets.maxModelFileSizeBytes).toBe( + contract.budgets.max_model_file_size_bytes); + expect(budgets.softModelFileSizeBytes).toBe( + contract.budgets.soft_model_file_size_bytes); + expect(budgets.maxTextureSize).toBe(contract.budgets.max_texture_size); + expect(budgets.softTextureSize).toBe(contract.budgets.soft_texture_size); + expect(budgets.maxBoneCount).toBe(contract.budgets.max_bone_count); + expect(budgets.softBoneCount).toBe(contract.budgets.soft_bone_count); + expect(budgets.maxCubeCount).toBe(contract.budgets.max_cube_count); + expect(budgets.softCubeCount).toBe(contract.budgets.soft_cube_count); + expect(budgets.maxHierarchyDepth).toBe( + contract.budgets.max_hierarchy_depth); + expect(budgets.softHierarchyDepth).toBe( + contract.budgets.soft_hierarchy_depth); + expect(budgets.maxAnimationCount).toBe( + contract.budgets.max_animation_count); + }); +}); + +describe('mod example style stays minimal', () => { + test('little_explorer style preset does not expand defaults', () => { + const settings = applyTemplate('humanoid_wandering'); + settings.namespace = 'easy_model_entities_examples'; + settings.profileId = 'little_explorer'; + + expect(buildServerProfile(settings)).toEqual({ + schema_version: '0.1.0', + model_type: 'entity', + preset_type: 'humanoid_wandering', + client: {render_profile: 'easy_model_entities_examples:little_explorer'} + }); + expect(buildRenderProfile(settings)).toEqual({ + schema_version: '0.1.0', + preset_type: 'humanoid_wandering' + }); + }); + + test('shrine style block entity keeps the block render override', () => { + const settings = applyTemplate('animated_randomly', 'block_entity'); + settings.namespace = 'easy_model_entities_examples'; + settings.profileId = 'shrine'; + + expect(buildServerProfile(settings)).toEqual({ + schema_version: '0.1.0', + model_type: 'block_entity', + preset_type: 'animated_randomly', + client: {render_profile: 'easy_model_entities_examples:shrine'} + }); + expect(buildRenderProfile(settings)).toEqual({ + schema_version: '0.1.0', + preset_type: 'static', + rendering: {shadow_radius: 0.5}, + animation: {mode: 'random_idle'} + }); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/exportDialog.test.js b/plugins/easy_model_entities/src/tests/exportDialog.test.js index 4bfffb93..c9fa5887 100644 --- a/plugins/easy_model_entities/src/tests/exportDialog.test.js +++ b/plugins/easy_model_entities/src/tests/exportDialog.test.js @@ -47,22 +47,27 @@ describe('exportDialog mapping', () => { }); describe('presetOptions', () => { - test('without experimental, only stable entity presets are shown', () => { - const options = presetOptions('entity', false); - expect(Object.keys(options)).toEqual([ - 'static', 'statue', 'humanoid_still', 'humanoid_wandering', - 'quadruped_still', 'quadruped_wandering' - ]); - expect(options.custom).toBeUndefined(); - expect(options.floating_still).toBeUndefined(); - }); + test('without experimental, every selectable preset is shown (custom hidden)', + () => { + const options = presetOptions('entity', false); + expect(Object.keys(options)).toEqual([ + 'static', 'statue', 'humanoid_still', 'humanoid_wandering', + 'quadruped_still', 'quadruped_wandering', 'aquatic_still', + 'aquatic_swimming', 'amphibious_still', 'amphibious_wandering', + 'winged_still', 'winged_wandering', 'winged_humanoid_still', + 'winged_humanoid_wandering', 'arthropod_still', 'arthropod_wandering', + 'cuboid_still', 'cuboid_hopping', 'floating_still' + ]); + expect(options.custom).toBeUndefined(); + expect(options.floating_still).toBeDefined(); + }); - test('experimental reveals custom and every selectable preset, no WIP mark', + test('experimental reveals custom on top of every selectable preset', () => { const options = presetOptions('entity', true); expect(Object.keys(options)[0]).toBe('custom'); - expect(Object.keys(options)).toHaveLength(18); - expect(options.floating_still).toBeDefined(); + expect(Object.keys(options)).toHaveLength(20); + expect(options.amphibious_wandering).toBeDefined(); expect(options.floating_still).not.toMatch(/WIP/); }); diff --git a/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.1.0.json b/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.1.0.json new file mode 100644 index 00000000..79e6a832 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.1.0.json @@ -0,0 +1,118 @@ +{ + "schema_version": "0.1.0", + "budgets": { + "max_model_file_size_bytes": 2097152, + "soft_model_file_size_bytes": 1048576, + "max_texture_size": 2048, + "soft_texture_size": 128, + "max_bone_count": 128, + "soft_bone_count": 96, + "max_cube_count": 512, + "soft_cube_count": 384, + "max_hierarchy_depth": 32, + "soft_hierarchy_depth": 24, + "max_animation_count": 16 + }, + "enums": { + "model_types": [ + "entity", + "block_entity" + ], + "body_types": [ + "static", + "biped", + "quadruped", + "aquatic", + "amphibious", + "winged", + "winged_humanoid", + "arthropod", + "cuboid", + "floating" + ], + "movement_types": [ + "ground", + "water", + "amphibious", + "static" + ], + "behavior_modes": [ + "idle_only", + "ambient", + "static", + "external_owner" + ], + "preset_types": [ + "custom", + "static", + "statue", + "aquatic_still", + "aquatic_swimming", + "amphibious_still", + "amphibious_wandering", + "arthropod_still", + "arthropod_wandering", + "cuboid_hopping", + "cuboid_still", + "floating_still", + "humanoid_still", + "humanoid_wandering", + "quadruped_still", + "quadruped_wandering", + "winged_humanoid_still", + "winged_humanoid_wandering", + "winged_still", + "winged_wandering" + ], + "block_entity_preset_types": [ + "static", + "ticking", + "animated", + "animated_randomly" + ], + "animation_modes": [ + "automatic", + "random_idle", + "none" + ], + "gaits": [ + "natural", + "feline", + "ungulate" + ] + }, + "status_codes": { + "server": [ + "ACTIVE", + "INVALID_JSON", + "INVALID_SCHEMA_VERSION", + "INVALID_MODEL_TYPE", + "INVALID_RESOURCE_LOCATION", + "INVALID_DIMENSIONS", + "INVALID_HOST_ENTITY", + "INVALID_HOST_BLOCK_ENTITY", + "DISABLED" + ], + "render": [ + "ACTIVE", + "INVALID_JSON", + "INVALID_SCHEMA_VERSION", + "INVALID_RESOURCE_LOCATION", + "INVALID_BODY_TYPE", + "INVALID_ANIMATION_MODE", + "INVALID_RENDER_SETTINGS", + "MISSING_RENDER_PROFILE", + "MISSING_MODEL", + "MISSING_TEXTURE", + "MODEL_DECODE_FAILED", + "CLIENT_ASSET_MISMATCH", + "CLIENT_BODY_TYPE_MISMATCH", + "FALLBACK_ACTIVE" + ] + }, + "diagnostic_severities": [ + "INFO", + "WARNING", + "ERROR" + ] +} diff --git a/plugins/easy_model_entities/src/tests/modUpdate.test.js b/plugins/easy_model_entities/src/tests/modUpdate.test.js new file mode 100644 index 00000000..e71dd2aa --- /dev/null +++ b/plugins/easy_model_entities/src/tests/modUpdate.test.js @@ -0,0 +1,145 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {presetDefaults} = require('../model/presetTypes'); +const {applyTemplate, pickModelSettings} = require('../model/templates'); +const {buildRenderProfile} = require('../builders/renderProfile'); +const {VisibleBounds} = require('../model/VisibleBounds'); + +describe('amphibious / aquatic mapping mirrors the mod', () => { + test('amphibious_wandering uses the amphibious host and movement defaults', + () => { + const d = presetDefaults('amphibious_wandering'); + expect(d.host).toEqual({ + entityType: 'easy_model_entities:amphibious_entity', + movementType: 'amphibious', + bodyType: 'amphibious' + }); + expect(d.movement).toEqual( + {speed: 0.15, stepHeight: 0.6, gravity: true}); + expect(d.behavior).toMatchObject( + {mode: 'ambient', randomStroll: true}); + expect(d.dimensions).toEqual({width: 0.9, height: 0.6, eyeHeight: 0.4}); + }); + + test('aquatic presets use the water host and movement defaults', () => { + const swimming = presetDefaults('aquatic_swimming'); + expect(swimming.host.entityType).toBe('easy_model_entities:aquatic_entity'); + expect(swimming.host.movementType).toBe('water'); + expect(swimming.movement).toEqual( + {speed: 0.1, stepHeight: 0, gravity: true}); + expect(swimming.behavior).toMatchObject( + {mode: 'ambient', randomStroll: true}); + + const still = presetDefaults('aquatic_still'); + expect(still.host.movementType).toBe('water'); + expect(still.behavior.mode).toBe('idle_only'); + }); +}); + +describe('pickModelSettings keeps reopen-critical settings', () => { + test('keeps customize / targetVersion and drops export-only keys', () => { + const full = { + ...applyTemplate('humanoid_wandering'), + targetVersion: '1.21.11', + customize: true, + exportType: 'packs', + exportTarget: 'packs', + modelOnly: false, + experimental: true + }; + const picked = pickModelSettings(full); + expect(picked.targetVersion).toBe('1.21.11'); + expect(picked.customize).toBe(true); + expect(picked.exportType).toBeUndefined(); + expect(picked.exportTarget).toBeUndefined(); + expect(picked.modelOnly).toBeUndefined(); + expect(picked.experimental).toBeUndefined(); + // Model-defining data is retained. + expect(picked.schemaVersion).toBe('0.1.0'); + expect(picked.presetType).toBe('humanoid_wandering'); + expect(picked.host).toEqual(full.host); + expect(picked.rendering).toEqual(full.rendering); + expect(picked.animation).toEqual(full.animation); + }); +}); + +describe('render profile emits the new optional fields when non-default', + () => { + test('gait, idle_strength and visible_bounds are written', () => { + const settings = applyTemplate('humanoid_wandering'); + settings.rendering.visibleBoundsWidth = 2; + settings.rendering.visibleBoundsHeight = 3; + settings.animation.idleStrength = 1.5; + settings.animation.gait = 'feline'; + + const render = buildRenderProfile(settings, null); + expect(render.rendering.visible_bounds_width).toBe(2); + expect(render.rendering.visible_bounds_height).toBe(3); + expect(render.rendering.visible_bounds_offset).toBeUndefined(); + expect(render.animation.idle_strength).toBe(1.5); + expect(render.animation.gait).toBe('feline'); + }); + + test('defaults (natural gait, idle 1, zero bounds) stay omitted', () => { + const render = buildRenderProfile(applyTemplate('humanoid_wandering'), + null); + // Every value matches the preset defaults, so neither section is written. + expect(render.rendering).toBeUndefined(); + expect(render.animation).toBeUndefined(); + }); + }); + +describe('VisibleBounds', () => { + test('derives a conservative box from a single cube', () => { + expect(VisibleBounds.derive([{from: [-8, 0, -8], to: [8, 16, 8]}])) + .toEqual({width: 1, height: 1, offset: [0, 0, 0]}); + }); + + test('includes thin / far protrusions instead of trimming them', () => { + const cubes = [ + {from: [0, 0, 0], to: [16, 16, 16]}, + {from: [7, 16, 8], to: [8, 48, 9]} + ]; + const derived = VisibleBounds.derive(cubes); + expect(derived.height).toBe(3); + expect(derived.width).toBe(1); + expect(derived.offset).toEqual([0.5, 0, 0.5]); + }); + + test('empty geometry falls back to vanilla culling (0 / 0)', () => { + expect(VisibleBounds.derive([])).toEqual( + {width: 0, height: 0, offset: [0, 0, 0]}); + }); + + test('applyVisibleBounds can preserve a saved manual override', () => { + const settings = applyTemplate('humanoid_wandering'); + settings.rendering.visibleBoundsWidth = 2; + settings.rendering.visibleBoundsHeight = 3; + settings.rendering.visibleBoundsOffset = [0, 1, 0]; + + VisibleBounds.applyVisibleBounds(settings, + {width: 1, height: 1, offset: [0, 0, 0]}, + {preserveExisting: true}); + + expect(settings.rendering.visibleBoundsWidth).toBe(2); + expect(settings.rendering.visibleBoundsHeight).toBe(3); + expect(settings.rendering.visibleBoundsOffset).toEqual([0, 1, 0]); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/packMeta.test.js b/plugins/easy_model_entities/src/tests/packMeta.test.js new file mode 100644 index 00000000..dfabe0ef --- /dev/null +++ b/plugins/easy_model_entities/src/tests/packMeta.test.js @@ -0,0 +1,62 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const { + buildDataPackMcmeta, + buildResourcePackMcmeta +} = require('../builders/packMeta'); + +describe('packMeta', () => { + test('legacy versions emit a single integer pack_format', () => { + expect(buildDataPackMcmeta({targetVersion: '1.20.1'}).pack) + .toMatchObject({pack_format: 15}); + expect(buildResourcePackMcmeta({targetVersion: '1.21.1'}).pack) + .toMatchObject({pack_format: 34}); + expect(buildDataPackMcmeta({targetVersion: '1.20.1'}).pack.min_format) + .toBeUndefined(); + }); + + test('1.21.11 emits pack_format plus min/max format ranges', () => { + const data = buildDataPackMcmeta({targetVersion: '1.21.11'}).pack; + expect(data.pack_format).toBe(94); + expect(data.min_format).toEqual([94, 1]); + expect(data.max_format).toEqual([94, 1]); + + const resource = buildResourcePackMcmeta({targetVersion: '1.21.11'}).pack; + expect(resource.pack_format).toBe(75); + expect(resource.min_format).toEqual([75, 0]); + expect(resource.max_format).toEqual([75, 0]); + }); + + test('26.1.2 emits pack_format plus min/max format ranges', () => { + const data = buildDataPackMcmeta({targetVersion: '26.1.2'}).pack; + expect(data.pack_format).toBe(101); + expect(data.min_format).toEqual([101, 1]); + expect(data.max_format).toEqual([101, 1]); + + const resource = buildResourcePackMcmeta({targetVersion: '26.1.2'}).pack; + expect(resource.pack_format).toBe(84); + expect(resource.min_format).toEqual([84, 0]); + expect(resource.max_format).toEqual([84, 0]); + }); + + test('unknown versions throw', () => { + expect(() => buildDataPackMcmeta({targetVersion: 'nope'})).toThrow(); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/templates.test.js b/plugins/easy_model_entities/src/tests/templates.test.js index 363ff18c..4d8cca34 100644 --- a/plugins/easy_model_entities/src/tests/templates.test.js +++ b/plugins/easy_model_entities/src/tests/templates.test.js @@ -28,16 +28,18 @@ const { const {presetDefaults} = require('../model/presetTypes'); describe('templates', () => { - test('exposes the 18 mod preset types', () => { - expect(PRESET_TYPES).toHaveLength(18); + test('exposes the 20 mod preset types', () => { + expect(PRESET_TYPES).toHaveLength(20); expect(PRESET_TYPES).toContain('custom'); expect(PRESET_TYPES).toContain('quadruped_wandering'); + expect(PRESET_TYPES).toContain('amphibious_still'); + expect(PRESET_TYPES).toContain('amphibious_wandering'); expect(PRESET_TYPES).toContain('floating_still'); }); test('selectable presets exclude custom', () => { expect(SELECTABLE_PRESET_TYPES).not.toContain('custom'); - expect(SELECTABLE_PRESET_TYPES).toHaveLength(17); + expect(SELECTABLE_PRESET_TYPES).toHaveLength(19); }); test('getDefaults carries identity fields and the default preset', () => { diff --git a/plugins/easy_model_entities/src/tests/textureResolver.test.js b/plugins/easy_model_entities/src/tests/textureResolver.test.js index ab23f9b9..36f5a78b 100644 --- a/plugins/easy_model_entities/src/tests/textureResolver.test.js +++ b/plugins/easy_model_entities/src/tests/textureResolver.test.js @@ -19,7 +19,8 @@ const { parseExternalLocation, - resolveTextures + resolveTextures, + describeTextureSource } = require('../utils/TextureResolver'); const SETTINGS = {namespace: 'example', profileId: 'lizard'}; @@ -49,6 +50,29 @@ describe('parseExternalLocation', () => { namespace: '', folder: 'block', name: 'eye_texture', path: '' })).toBeNull(); }); + + test('keeps a non-minecraft assets path custom when no namespace is set', + () => { + expect(parseExternalLocation({ + namespace: '', folder: 'block', name: 'disguised_chestling', + path: 'D:/x/assets/easy_model_entities_examples/textures/entity/' + + 'disguised_chestling_1.png' + })).toBeNull(); + }); +}); + +describe('describeTextureSource', () => { + test('labels a no-namespace texture as custom', () => { + expect(describeTextureSource({ + namespace: '', folder: 'block', name: 'disguised_chestling.png', path: '' + })).toEqual({external: false, label: 'Custom Texture'}); + }); + + test('labels a namespaced texture with its compact location', () => { + expect(describeTextureSource({ + namespace: 'minecraft', folder: 'entity/chest', name: 'normal.png' + })).toEqual({external: true, label: 'minecraft:entity/chest/normal'}); + }); }); describe('resolveTextures', () => { @@ -64,6 +88,42 @@ describe('resolveTextures', () => { expect(result.packed).toEqual([{fileName: 'lizard.png', bytes: BYTES}]); }); + test('foreign-namespace index 0 is referenced, not packed', () => { + const result = resolveTextures( + [{ + index: 0, namespace: 'othermod', folder: 'entity', name: 'thing', + path: '', bytes: BYTES + }], + SETTINGS); + expect(result.texture).toBe('othermod:textures/entity/thing.png'); + expect(result.textures).toEqual({}); + expect(result.packed).toEqual([]); + }); + + test('packs a custom index 1 that lives under a non-minecraft assets tree', + () => { + const result = resolveTextures([ + { + index: 0, namespace: 'minecraft', folder: 'entity/chest', + name: 'normal.png', path: '', bytes: BYTES + }, + { + index: 1, namespace: '', folder: 'block', + name: 'disguised_chestling.png', + path: 'D:/x/assets/easy_model_entities_examples/textures/entity/' + + 'disguised_chestling_1.png', + bytes: BYTES + } + ], SETTINGS); + + expect(result.texture).toBe( + 'minecraft:textures/entity/chest/normal.png'); + expect(result.textures).toEqual( + {1: 'example:textures/entity/lizard_1.png'}); + expect(result.packed).toEqual( + [{fileName: 'lizard_1.png', bytes: BYTES}]); + }); + test('mixes a vanilla index 0 with a custom index 1', () => { const result = resolveTextures([ { diff --git a/plugins/easy_model_entities/src/tests/validation.test.js b/plugins/easy_model_entities/src/tests/validation.test.js index 91436662..151ea4bf 100644 --- a/plugins/easy_model_entities/src/tests/validation.test.js +++ b/plugins/easy_model_entities/src/tests/validation.test.js @@ -101,16 +101,40 @@ describe('validateSettings', () => { cubeCount: 1000, boneCount: 200, hierarchyDepth: 50, + animationCount: 17, boneNames: QUADRUPED_BONES }); expect(result.valid).toBe(true); expect(codes(result.warnings)).toEqual( expect.arrayContaining( ['LARGE_TEXTURE', 'LARGE_MODEL', 'HIGH_CUBE_COUNT', - 'HIGH_BONE_COUNT', 'DEEP_HIERARCHY']) + 'HIGH_BONE_COUNT', 'DEEP_HIERARCHY', + 'HIGH_ANIMATION_COUNT']) ); }); + test('warns about soft performance budgets without blocking', () => { + const result = Validator.validateSettings(fixtureSettings(), { + hasModel: true, + hasTexture: true, + textureWidth: 256, + textureHeight: 128, + modelFileSize: 1536 * 1024, + cubeCount: 400, + boneCount: 100, + hierarchyDepth: 25, + animationCount: 16, + boneNames: QUADRUPED_BONES + }); + expect(result.valid).toBe(true); + expect(codes(result.warnings)).toEqual( + expect.arrayContaining( + ['SOFT_TEXTURE_SIZE', 'SOFT_MODEL_SIZE', 'SOFT_CUBE_COUNT', + 'SOFT_BONE_COUNT', 'SOFT_HIERARCHY_DEPTH']) + ); + expect(codes(result.warnings)).not.toContain('HIGH_ANIMATION_COUNT'); + }); + test('warns about missing body parts per body type', () => { const result = Validator.validateSettings(fixtureSettings(), {hasModel: true, hasTexture: true, boneNames: ['root', 'body']}); diff --git a/plugins/easy_model_entities/src/tests/versionMatrix.test.js b/plugins/easy_model_entities/src/tests/versionMatrix.test.js index 78b04501..41eea936 100644 --- a/plugins/easy_model_entities/src/tests/versionMatrix.test.js +++ b/plugins/easy_model_entities/src/tests/versionMatrix.test.js @@ -25,10 +25,10 @@ const { } = require('../model/versionMatrix'); describe('versionMatrix', () => { - test('default version is 1.20.1 and enabled', () => { + test('default version is 1.20.1; all four versions are enabled', () => { expect(getDefaultVersionId()).toBe('1.20.1'); const enabled = getEnabledVersions().map((v) => v.id); - expect(enabled).toEqual(['1.20.1']); + expect(enabled).toEqual(['1.20.1', '1.21.1', '1.21.11', '26.1.2']); }); test('exposes the full selectable version list', () => { @@ -36,13 +36,28 @@ describe('versionMatrix', () => { ['1.20.1', '1.21.1', '1.21.11', '26.1.2']); }); - test('1.20.1 maps to pack_format 15 / 15', () => { + test('legacy versions use a single integer pack_format', () => { expect(getPackFormats('1.20.1')).toEqual( - {dataFormat: 15, resourceFormat: 15}); + {data: {packFormat: 15}, resource: {packFormat: 15}}); + expect(getPackFormats('1.21.1')).toEqual( + {data: {packFormat: 48}, resource: {packFormat: 34}}); }); - test('disabled versions return no pack formats', () => { - expect(getPackFormats('1.21.1')).toBeNull(); + test('1.21.11 uses the min/max format scheme', () => { + expect(getPackFormats('1.21.11')).toEqual({ + data: {packFormat: 94, minFormat: [94, 1], maxFormat: [94, 1]}, + resource: {packFormat: 75, minFormat: [75, 0], maxFormat: [75, 0]} + }); + }); + + test('26.1.2 uses the min/max format scheme', () => { + expect(getPackFormats('26.1.2')).toEqual({ + data: {packFormat: 101, minFormat: [101, 1], maxFormat: [101, 1]}, + resource: {packFormat: 84, minFormat: [84, 0], maxFormat: [84, 0]} + }); + }); + + test('unknown versions return no pack formats', () => { expect(getPackFormats('does-not-exist')).toBeNull(); }); }); diff --git a/plugins/easy_model_entities/src/ui/exportDialog.js b/plugins/easy_model_entities/src/ui/exportDialog.js index ec00f3db..04aa443e 100644 --- a/plugins/easy_model_entities/src/ui/exportDialog.js +++ b/plugins/easy_model_entities/src/ui/exportDialog.js @@ -17,119 +17,30 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const {getVersions} = require('../model/versionMatrix'); const {getDefaults, applyTemplate, deepMerge} = require('../model/templates'); const { - SELECTABLE_PRESET_TYPES, - BLOCK_ENTITY_PRESET_TYPES, - isStablePreset, MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY } = require('../model/presetTypes'); const {ModelDimensions} = require('../model/ModelDimensions'); +const {VisibleBounds} = require('../model/VisibleBounds'); const {t} = require('../i18n/translations'); - -function presetLabel(id) { - return id === 'custom' ? t('eme.preset.custom') : t(`eme.preset.${id}`); -} - -function entityPresetIds(experimental) { - return experimental - ? ['custom', ...SELECTABLE_PRESET_TYPES] - : SELECTABLE_PRESET_TYPES.filter(isStablePreset); -} - -function blockPresetIds(experimental) { - return experimental - ? BLOCK_ENTITY_PRESET_TYPES.slice() - : BLOCK_ENTITY_PRESET_TYPES.filter(isStablePreset); -} - -// Experimental (WIP) presets are hidden unless the experimental setting is on. -// `ensure` keeps an already-selected preset visible even when experimental is -// off, so reopening a project that used one never breaks the dropdown. -function presetOptions(modelType, experimental, ensure) { - const ids = modelType === MODEL_TYPE_BLOCK_ENTITY - ? blockPresetIds(experimental) : entityPresetIds(experimental); - if (ensure && !ids.includes(ensure)) { - ids.push(ensure); - } - const options = {}; - ids.forEach((id) => { - options[id] = presetLabel(id); - }); - - return options; -} - -function modelTypeOptions() { - return { - [MODEL_TYPE_ENTITY]: t('eme.modelType.entity'), - [MODEL_TYPE_BLOCK_ENTITY]: t('eme.modelType.block_entity') - }; -} - -function exportTypeOptions() { - return { - packs: t('eme.exportType.packs'), - mod_project: t('eme.exportType.mod_project'), - model_only: t('eme.exportType.model_only') - }; -} - -function hostEntityTypeOptions() { - return { - 'easy_model_entities:ground_entity': t('eme.entity.ground_entity'), - 'easy_model_entities:static_entity': t('eme.entity.static_entity') - }; -} - -function movementTypeOptions() { - return {ground: t('eme.movement.ground'), static: t('eme.movement.static')}; -} - -function bodyTypeOptions() { - return { - static: t('eme.body.static'), - biped: t('eme.body.biped'), - quadruped: t('eme.body.quadruped'), - aquatic: t('eme.body.aquatic'), - winged: t('eme.body.winged'), - winged_humanoid: t('eme.body.winged_humanoid'), - arthropod: t('eme.body.arthropod'), - cuboid: t('eme.body.cuboid'), - floating: t('eme.body.floating') - }; -} - -function behaviorModeOptions() { - return { - idle_only: t('eme.behavior.idle_only'), - ambient: t('eme.behavior.ambient'), - static: t('eme.behavior.static'), - external_owner: t('eme.behavior.external_owner') - }; -} - -function animationModeOptions() { - return { - automatic: t('eme.animation.automatic'), - random_idle: t('eme.animation.random_idle'), - none: t('eme.animation.none') - }; -} - -function versionOptions() { - const options = {}; - getVersions().forEach((version) => { - options[version.id] = version.enabled ? version.label - : `${version.label} (coming soon)`; - }); - - return options; -} +const { + presetOptions, + modelTypeOptions, + exportTypeOptions, + hostEntityTypeOptions, + movementTypeOptions, + bodyTypeOptions, + gaitOptions, + behaviorModeOptions, + animationModeOptions, + versionOptions +} = require('./exportDialogOptions'); function settingsToForm(settings) { + const offset = settings.rendering.visibleBoundsOffset || [0, 0, 0]; + return { namespace: settings.namespace, profileId: settings.profileId, @@ -149,9 +60,16 @@ function settingsToForm(settings) { followRange: settings.attributes.followRange, scale: settings.rendering.scale, shadowRadius: settings.rendering.shadowRadius, + visibleBoundsWidth: settings.rendering.visibleBoundsWidth ?? 0, + visibleBoundsHeight: settings.rendering.visibleBoundsHeight ?? 0, + visibleBoundsOffsetX: offset[0], + visibleBoundsOffsetY: offset[1], + visibleBoundsOffsetZ: offset[2], animationMode: settings.animation.mode, swingSpeed: settings.animation.swingSpeed, - walkSpeedMultiplier: settings.animation.walkSpeedMultiplier + walkSpeedMultiplier: settings.animation.walkSpeedMultiplier, + idleStrength: settings.animation.idleStrength ?? 1, + gait: settings.animation.gait || 'natural' }; } @@ -190,19 +108,25 @@ function formToSettings(form, base) { }, rendering: { scale: Number(form.scale), - shadowRadius: Number(form.shadowRadius) + shadowRadius: Number(form.shadowRadius), + visibleBoundsWidth: Number(form.visibleBoundsWidth), + visibleBoundsHeight: Number(form.visibleBoundsHeight), + visibleBoundsOffset: [ + Number(form.visibleBoundsOffsetX), + Number(form.visibleBoundsOffsetY), + Number(form.visibleBoundsOffsetZ) + ] }, animation: { mode: form.animationMode, swingSpeed: Number(form.swingSpeed), - walkSpeedMultiplier: Number(form.walkSpeedMultiplier) + walkSpeedMultiplier: Number(form.walkSpeedMultiplier), + idleStrength: Number(form.idleStrength), + gait: form.gait } }; } -// Active preset depends on whether a block entity datapack is being authored. -// Model-only export still authors a render (body) preset, so it uses the entity -// preset list. function activeModelType(form) { return form.exportType === 'model_only' ? MODEL_TYPE_ENTITY : (form.modelType || MODEL_TYPE_ENTITY); @@ -213,9 +137,12 @@ function activePreset(form) { ? (form.blockPreset || 'static') : (form.preset || 'custom'); } -function presetFormValues(presetType, modelType, modelDimensions) { - const form = settingsToForm(ModelDimensions.applyModelDimensions( - applyTemplate(presetType, modelType), modelDimensions)); +function presetFormValues(presetType, modelType, modelDimensions, + visibleBounds) { + const settings = ModelDimensions.applyModelDimensions( + applyTemplate(presetType, modelType), modelDimensions); + VisibleBounds.applyVisibleBounds(settings, visibleBounds); + const form = settingsToForm(settings); delete form.namespace; delete form.profileId; delete form.targetVersion; @@ -223,7 +150,7 @@ function presetFormValues(presetType, modelType, modelDimensions) { return form; } -function resolveExportSettings(form, base, modelDimensions) { +function resolveExportSettings(form, base, modelDimensions, visibleBounds) { const exportType = form.exportType || 'packs'; const modelType = activeModelType(form); const preset = activePreset(form); @@ -234,6 +161,7 @@ function resolveExportSettings(form, base, modelDimensions) { } else { settings = ModelDimensions.applyModelDimensions( applyTemplate(preset, modelType), modelDimensions); + VisibleBounds.applyVisibleBounds(settings, visibleBounds); } settings.schemaVersion = base.schemaVersion || settings.schemaVersion; @@ -433,6 +361,31 @@ function buildFormConfig(settings, ui) { label: t('eme.field.shadowRadius'), type: 'number', value: values.shadowRadius, step: 0.1 }, showRender), + visibleBoundsWidth: advancedField( + { + label: t('eme.field.visibleBoundsWidth'), type: 'number', + value: values.visibleBoundsWidth, step: 0.1 + }, showRender), + visibleBoundsHeight: advancedField( + { + label: t('eme.field.visibleBoundsHeight'), type: 'number', + value: values.visibleBoundsHeight, step: 0.1 + }, showRender), + visibleBoundsOffsetX: advancedField( + { + label: t('eme.field.visibleBoundsOffsetX'), type: 'number', + value: values.visibleBoundsOffsetX, step: 0.1 + }, showRender), + visibleBoundsOffsetY: advancedField( + { + label: t('eme.field.visibleBoundsOffsetY'), type: 'number', + value: values.visibleBoundsOffsetY, step: 0.1 + }, showRender), + visibleBoundsOffsetZ: advancedField( + { + label: t('eme.field.visibleBoundsOffsetZ'), type: 'number', + value: values.visibleBoundsOffsetZ, step: 0.1 + }, showRender), animation_header: advancedField( {type: 'info', text: `### ${t('eme.section.animation')}`}, @@ -452,7 +405,18 @@ function buildFormConfig(settings, ui) { { label: t('eme.field.walkSpeedMultiplier'), type: 'number', value: values.walkSpeedMultiplier, step: 0.1 - }, showRender) + }, showRender), + idleStrength: advancedField( + { + label: t('eme.field.idleStrength'), type: 'number', + value: values.idleStrength, step: 0.1 + }, showRender), + gait: advancedField({ + label: t('eme.field.gait'), + type: 'select', + options: gaitOptions(), + value: values.gait + }, showRender) }); return config; @@ -461,8 +425,7 @@ function buildFormConfig(settings, ui) { function openExportDialog(options) { const settings = options.settings; const modelDimensions = options.modelDimensions; - // Tracks the active model type + preset so advanced fields refill whenever the - // user switches between entity/block entity or picks a different preset. + const visibleBounds = options.visibleBounds; let lastKey = `${activeModelType(options)}|${activePreset(options)}`; const dialog = new Dialog({ @@ -486,13 +449,14 @@ function openExportDialog(options) { lastKey = key; if (modelType !== MODEL_TYPE_ENTITY || preset !== 'custom') { dialog.setFormValues( - presetFormValues(preset, modelType, modelDimensions), false); + presetFormValues(preset, modelType, modelDimensions, + visibleBounds), false); } } }, onConfirm(form) { const finalSettings = resolveExportSettings(form, settings, - modelDimensions); + modelDimensions, visibleBounds); // Records that experimental presets were available for this export. finalSettings.experimental = !!options.experimental; options.onExport(finalSettings, finalSettings.exportTarget); diff --git a/plugins/easy_model_entities/src/ui/exportDialogOptions.js b/plugins/easy_model_entities/src/ui/exportDialogOptions.js new file mode 100644 index 00000000..1cb0f200 --- /dev/null +++ b/plugins/easy_model_entities/src/ui/exportDialogOptions.js @@ -0,0 +1,155 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {getVersions} = require('../model/versionMatrix'); +const { + SELECTABLE_PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + isStablePreset, + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY +} = require('../model/presetTypes'); +const {t} = require('../i18n/translations'); + +function presetLabel(id) { + return id === 'custom' ? t('eme.preset.custom') : t(`eme.preset.${id}`); +} + +function entityPresetIds(experimental) { + return experimental + ? ['custom', ...SELECTABLE_PRESET_TYPES] + : SELECTABLE_PRESET_TYPES.filter(isStablePreset); +} + +function blockPresetIds(experimental) { + return experimental + ? BLOCK_ENTITY_PRESET_TYPES.slice() + : BLOCK_ENTITY_PRESET_TYPES.filter(isStablePreset); +} + +// Experimental (WIP) presets are hidden unless the experimental setting is on. +function presetOptions(modelType, experimental, ensure) { + const ids = modelType === MODEL_TYPE_BLOCK_ENTITY + ? blockPresetIds(experimental) : entityPresetIds(experimental); + if (ensure && !ids.includes(ensure)) { + ids.push(ensure); + } + const options = {}; + ids.forEach((id) => { + options[id] = presetLabel(id); + }); + + return options; +} + +function modelTypeOptions() { + return { + [MODEL_TYPE_ENTITY]: t('eme.modelType.entity'), + [MODEL_TYPE_BLOCK_ENTITY]: t('eme.modelType.block_entity') + }; +} + +function exportTypeOptions() { + return { + packs: t('eme.exportType.packs'), + mod_project: t('eme.exportType.mod_project'), + model_only: t('eme.exportType.model_only') + }; +} + +function hostEntityTypeOptions() { + return { + 'easy_model_entities:ground_entity': t('eme.entity.ground_entity'), + 'easy_model_entities:static_entity': t('eme.entity.static_entity'), + 'easy_model_entities:aquatic_entity': t('eme.entity.aquatic_entity'), + 'easy_model_entities:amphibious_entity': t('eme.entity.amphibious_entity') + }; +} + +function movementTypeOptions() { + return { + ground: t('eme.movement.ground'), + water: t('eme.movement.water'), + amphibious: t('eme.movement.amphibious'), + static: t('eme.movement.static') + }; +} + +function bodyTypeOptions() { + return { + static: t('eme.body.static'), + biped: t('eme.body.biped'), + quadruped: t('eme.body.quadruped'), + aquatic: t('eme.body.aquatic'), + amphibious: t('eme.body.amphibious'), + winged: t('eme.body.winged'), + winged_humanoid: t('eme.body.winged_humanoid'), + arthropod: t('eme.body.arthropod'), + cuboid: t('eme.body.cuboid'), + floating: t('eme.body.floating') + }; +} + +function gaitOptions() { + return { + natural: t('eme.gait.natural'), + feline: t('eme.gait.feline'), + ungulate: t('eme.gait.ungulate') + }; +} + +function behaviorModeOptions() { + return { + idle_only: t('eme.behavior.idle_only'), + ambient: t('eme.behavior.ambient'), + static: t('eme.behavior.static'), + external_owner: t('eme.behavior.external_owner') + }; +} + +function animationModeOptions() { + return { + automatic: t('eme.animation.automatic'), + random_idle: t('eme.animation.random_idle'), + none: t('eme.animation.none') + }; +} + +function versionOptions() { + const options = {}; + getVersions().forEach((version) => { + options[version.id] = version.enabled ? version.label + : `${version.label} (coming soon)`; + }); + + return options; +} + +module.exports = { + presetOptions, + modelTypeOptions, + exportTypeOptions, + hostEntityTypeOptions, + movementTypeOptions, + bodyTypeOptions, + gaitOptions, + behaviorModeOptions, + animationModeOptions, + versionOptions +}; diff --git a/plugins/easy_model_entities/src/ui/texturePanel.js b/plugins/easy_model_entities/src/ui/texturePanel.js new file mode 100644 index 00000000..04c7235b --- /dev/null +++ b/plugins/easy_model_entities/src/ui/texturePanel.js @@ -0,0 +1,72 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {describeTextureSource} = require('../utils/TextureResolver'); + +let patchedVue = null; +let originalGetDescription = null; + +function texturesPanelVue() { + const panel = (typeof Panels !== 'undefined' && Panels.textures) + || (typeof Interface !== 'undefined' && Interface.Panels + && Interface.Panels.textures); + + return (panel && panel.inside_vue) || null; +} + +function patchTexturePanel(formatId) { + const vue = texturesPanelVue(); + if (!vue || typeof vue.getDescription !== 'function' + || originalGetDescription) { + return; + } + + const original = vue.getDescription.bind(vue); + patchedVue = vue; + originalGetDescription = original; + vue.getDescription = function (texture) { + const base = original(texture); + try { + if (typeof Format !== 'undefined' && Format && Format.id === formatId) { + const {label} = describeTextureSource({ + namespace: texture.namespace, + folder: texture.folder, + name: texture.name, + path: texture.path + }); + + return `${base} - ${label}`; + } + } catch (error) { + // Display nicety only; never break the panel render. + } + + return base; + }; +} + +function unpatchTexturePanel() { + if (patchedVue && originalGetDescription) { + patchedVue.getDescription = originalGetDescription; + } + patchedVue = null; + originalGetDescription = null; +} + +module.exports = {patchTexturePanel, unpatchTexturePanel}; diff --git a/plugins/easy_model_entities/src/utils/TextureResolver.js b/plugins/easy_model_entities/src/utils/TextureResolver.js index f5fd3a48..f8231309 100644 --- a/plugins/easy_model_entities/src/utils/TextureResolver.js +++ b/plugins/easy_model_entities/src/utils/TextureResolver.js @@ -47,26 +47,35 @@ function customLocation(settings, index) { } function parseExternalLocation(descriptor) { - const path = String(descriptor.path || '').replace(/\\/g, '/'); - const match = path.match(ASSETS_PATTERN); - if (match) { - return `${match[1].toLowerCase()}:textures/${match[2].toLowerCase()}.png`; - } - const namespace = String(descriptor.namespace || '').trim().toLowerCase(); - if (!namespace) { - return null; + if (namespace) { + const folder = trimSlashes(String(descriptor.folder || '').trim()); + const name = stripPng(String(descriptor.name || '').trim()).toLowerCase(); + if (!name) { + return null; + } + const rest = folder ? `${folder.toLowerCase()}/${name}` : name; + + return `${namespace}:textures/${rest}.png`; } - const folder = trimSlashes(String(descriptor.folder || '').trim()); - const name = stripPng(String(descriptor.name || '').trim()).toLowerCase(); - if (!name) { - return null; + const path = String(descriptor.path || '').replace(/\\/g, '/'); + const match = path.match(ASSETS_PATTERN); + if (match && match[1].toLowerCase() === 'minecraft') { + return `minecraft:textures/${match[2].toLowerCase()}.png`; } - const rest = folder ? `${folder.toLowerCase()}/${name}` : name; + return null; +} - return `${namespace}:textures/${rest}.png`; +function describeTextureSource(descriptor) { + const location = parseExternalLocation(descriptor); + return location + ? { + external: true, label: location.replace(':textures/', ':').replace( + /\.png$/i, '') + } + : {external: false, label: 'Custom Texture'}; } function resolveTextures(descriptors, settings) { @@ -87,8 +96,6 @@ function resolveTextures(descriptors, settings) { }); } - // Index 0 follows the mod's default path unless it deviates (e.g. vanilla); - // every other index is always spelled out in the textures map. if (index === 0) { if (location !== defaultLocation) { texture = location; @@ -101,4 +108,8 @@ function resolveTextures(descriptors, settings) { return {texture, textures, packed}; } -module.exports = {resolveTextures, parseExternalLocation}; +module.exports = { + resolveTextures, + parseExternalLocation, + describeTextureSource +}; From d0bf09b7a3b4cfd84718fbfe18be530676ef5b1c Mon Sep 17 00:00:00 2001 From: Markus Bordihn Date: Fri, 3 Jul 2026 20:47:55 +0200 Subject: [PATCH 5/8] Added animation clip support for idle, walk, swim and fly. Improved Animation validation. --- plugins.json | 2 +- plugins/easy_model_entities/changelog.json | 2 +- .../easy_model_entities.js | 613 ++++++++++-------- .../src/BlockbenchAdapter.js | 45 ++ .../src/builders/renderProfile.js | 58 +- .../src/builders/serverProfile.js | 11 +- .../src/format/EmeFormat.js | 4 +- plugins/easy_model_entities/src/index.js | 9 +- .../src/model/ModelDimensions.js | 6 +- .../src/model/PresetDetector.js | 5 - .../src/model/Validator.js | 34 +- .../src/model/presetTypes.js | 21 +- .../src/model/templates.js | 9 +- .../src/model/versionMatrix.js | 5 - .../easy_model_entities/src/package-lock.json | 377 +++++------ plugins/easy_model_entities/src/package.json | 2 +- .../src/tests/blockbenchAdapter.test.js | 54 ++ .../src/tests/contractSync.test.js | 3 + .../src/tests/exportDialog.test.js | 6 +- .../src/tests/fixtureData.js | 3 - .../tests/fixtures/easy-model-api-0.1.0.json | 6 + .../src/tests/schemaConsistency.test.js | 8 +- .../src/tests/templates.test.js | 8 +- .../src/tests/validation.test.js | 50 +- .../src/tests/versionMatrix.test.js | 3 +- .../src/ui/exportDialog.js | 164 +---- .../src/ui/exportDialogOptions.js | 56 +- .../src/ui/exportSettingsMapping.js | 179 +++++ 28 files changed, 955 insertions(+), 788 deletions(-) create mode 100644 plugins/easy_model_entities/src/tests/blockbenchAdapter.test.js create mode 100644 plugins/easy_model_entities/src/ui/exportSettingsMapping.js diff --git a/plugins.json b/plugins.json index efaaf73f..c3822b22 100644 --- a/plugins.json +++ b/plugins.json @@ -1541,7 +1541,7 @@ "icon": "icon.png", "description": "Export Blockbench models for the Easy Model Entities mod (Minecraft: Java Edition) as ready-to-install Data Pack and Resource Pack files.", "tags": ["Minecraft: Java Edition", "Format", "Exporter", "Entity"], - "version": "0.1.0", + "version": "1.0.0", "min_version": "4.9.0", "variant": "desktop", "await_loading": true, diff --git a/plugins/easy_model_entities/changelog.json b/plugins/easy_model_entities/changelog.json index 354ae6ad..1053d549 100644 --- a/plugins/easy_model_entities/changelog.json +++ b/plugins/easy_model_entities/changelog.json @@ -1,5 +1,5 @@ { - "0.1.0": { + "1.0.0": { "title": "Beta Release for testing and feedback", "author": "Markus Bordihn", "date": "2026-06-13", diff --git a/plugins/easy_model_entities/easy_model_entities.js b/plugins/easy_model_entities/easy_model_entities.js index 9a315226..c447ae18 100644 --- a/plugins/easy_model_entities/easy_model_entities.js +++ b/plugins/easy_model_entities/easy_model_entities.js @@ -23,6 +23,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +const {Validator} = __webpack_require__(229); + class BlockbenchAdapter { static PROJECT_PROPERTY = 'eme_export'; @@ -68,6 +70,43 @@ class BlockbenchAdapter { })); } + static #isNumericValue(value) { + if (typeof value === 'number') { + return Number.isFinite(value); + } + + return typeof value === 'string' && value.trim() !== '' + && Number.isFinite(Number(value)); + } + + static #summarizeAnimation(animation) { + const channels = new Set(); + let hasExpression = false; + + Object.values(animation.animators || {}).forEach((animator) => { + (animator.keyframes || []).forEach((keyframe) => { + if (keyframe.channel) { + channels.add(keyframe.channel); + } + (keyframe.data_points || []).forEach((point) => { + ['x', 'y', 'z'].forEach((axis) => { + const value = point?.[axis]; + if (value !== undefined && value !== null + && !BlockbenchAdapter.#isNumericValue(value)) { + hasExpression = true; + } + }); + }); + }); + }); + + return { + name: animation.name || '', + channels: [...channels], + hasExpression: hasExpression + }; + } + static getModelStats() { const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; const groups = typeof Group !== 'undefined' && Group.all ? Group.all : []; @@ -94,6 +133,8 @@ class BlockbenchAdapter { cubeCount: cubes.length, boneCount: groups.length, animationCount: animations.length, + animations: animations.map( + (animation) => BlockbenchAdapter.#summarizeAnimation(animation)), hierarchyDepth: maxDepth, boneNames: groups.map((group) => group.name), textureWidth: texture ? texture.width : undefined, @@ -233,6 +274,10 @@ class BlockbenchAdapter { const fs = __webpack_require__(896); const path = __webpack_require__(928); files.forEach((file) => { + const result = Validator.validateOutputPath(rootDir, file.path); + if (!result.valid) { + throw new Error(`${result.code}: ${result.message} (${file.path})`); + } const fullPath = path.join(rootDir, file.path); fs.mkdirSync(path.dirname(fullPath), {recursive: true}); const data = @@ -625,7 +670,8 @@ module.exports = {buildReadme}; const { bodyType, animationMode, - presetShadowRadius, + defaultRenderingSettings, + defaultAnimationSettings, isCustom, MODEL_TYPE_BLOCK_ENTITY } = __webpack_require__(151); @@ -636,40 +682,37 @@ function renderPresetType(settings) { ? 'static' : settings.presetType; } +function toRenderingJson(values) { + return { + scale: values.scale, + shadow_radius: values.shadowRadius, + visible_bounds_width: values.visibleBoundsWidth ?? 0, + visible_bounds_height: values.visibleBoundsHeight ?? 0, + visible_bounds_offset: values.visibleBoundsOffset || [0, 0, 0] + }; +} + function buildRendering(settings) { - const rendering = settings.rendering; - return diffFlat({ - scale: rendering.scale, - shadow_radius: rendering.shadowRadius, - visible_bounds_width: rendering.visibleBoundsWidth ?? 0, - visible_bounds_height: rendering.visibleBoundsHeight ?? 0, - visible_bounds_offset: rendering.visibleBoundsOffset || [0, 0, 0] - }, { - scale: 1, - shadow_radius: presetShadowRadius(renderPresetType(settings)), - visible_bounds_width: 0, - visible_bounds_height: 0, - visible_bounds_offset: [0, 0, 0] - }); + return diffFlat(toRenderingJson(settings.rendering), + toRenderingJson(defaultRenderingSettings(renderPresetType(settings)))); +} + +function toAnimationJson(values) { + return { + mode: values.mode, + swing_speed: values.swingSpeed, + walk_speed_multiplier: values.walkSpeedMultiplier, + idle_strength: values.idleStrength ?? 1, + gait: values.gait || 'natural' + }; } function buildAnimation(settings) { - const defaultMode = animationMode(renderPresetType(settings)); - const animation = diffFlat({ - mode: settings.animation.mode, - swing_speed: settings.animation.swingSpeed, - walk_speed_multiplier: settings.animation.walkSpeedMultiplier, - idle_strength: settings.animation.idleStrength ?? 1, - gait: settings.animation.gait || 'natural' - }, { - mode: defaultMode, - swing_speed: 1, - walk_speed_multiplier: 1, - idle_strength: 1, - gait: 'natural' - }); + const defaults = defaultAnimationSettings( + animationMode(renderPresetType(settings))); + const animation = diffFlat(toAnimationJson(settings.animation), + toAnimationJson(defaults)); - // Animation timing is meaningless when animation is disabled. if (settings.animation.mode === 'none') { delete animation.swing_speed; delete animation.walk_speed_multiplier; @@ -741,14 +784,13 @@ const { behaviorModeFor, wandersByMovement, isCustom, + DEFAULT_MAX_HEALTH, + DEFAULT_FOLLOW_RANGE, MODEL_TYPE_BLOCK_ENTITY, MODEL_TYPE_ENTITY } = __webpack_require__(151); const {diffFlat, assignIfPresent} = __webpack_require__(640); -const DEFAULT_MAX_HEALTH = 10; -const DEFAULT_FOLLOW_RANGE = 16; - function effectiveDefaults(settings) { const preset = settings.presetType; const movementType = settings.host.movementType; @@ -777,9 +819,6 @@ function effectiveDefaults(settings) { }; } -// Entity host settings. The mod derives type/movement_type/body_type from the -// preset, so only the "custom" preset needs to spell them out; non-custom -// presets emit nothing here. function buildEntity(settings, custom) { if (!custom) { return {}; @@ -823,9 +862,6 @@ function buildServerProfile(settings) { preset_type: settings.presetType }; - // The server profile lives in a "/" subfolder, so its derived - // id differs from the flat render profile id. The render profile must be named - // explicitly or the mod would look for "/" instead. profile.client = { render_profile: `${settings.namespace}:${settings.profileId}` }; @@ -993,9 +1029,7 @@ function registerEmeFormat(icon) { if (Project && !Project.model_identifier && Project.name) { Project.model_identifier = toModelIdentifier(Project.name); } - }, - onDeactivation() { - }, + } }); } @@ -1296,9 +1330,8 @@ class ModelDimensions { static #EYE_HEIGHT_FACTOR = {biped: 0.9, quadruped: 0.8, static: 0.85}; - static #round(value, digits) { - const factor = Math.pow(10, digits == null ? 3 : digits); - return Math.round(value * factor) / factor; + static #round(value) { + return Math.round(value * 1000) / 1000; } static #isUsableBounds(bounds) { @@ -1336,7 +1369,6 @@ class ModelDimensions { }; } - // mutates settings in-place and returns it static applyModelDimensions(settings, modelDimensions) { if (!modelDimensions) { return settings; @@ -1376,11 +1408,6 @@ module.exports = {ModelDimensions}; * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Suggests the most likely ModelPresetType from a model's bone names and bounds. -// Pure and side-effect free so it can be unit tested in isolation. Always -// suggests a "still" variant (the safe default); the user can switch to a -// moving variant in the dialog. - class PresetDetector { static #CUBE_RATIO = 1.4; @@ -1497,9 +1524,12 @@ const {ResourceLocation} = __webpack_require__(20); const { PRESET_TYPES, BLOCK_ENTITY_PRESET_TYPES, + ANIMATION_CLIPS, MODEL_TYPE_BLOCK_ENTITY } = __webpack_require__(151); +const SUPPORTED_ANIMATION_CHANNELS = new Set(['rotation', 'position']); + class Validator { static BUDGETS = { maxTextureSize: 2048, @@ -1581,8 +1611,6 @@ class Validator { }); } const blockEntity = settings.modelType === MODEL_TYPE_BLOCK_ENTITY; - - // Block entity host type is derived by the mod; only entities carry one. if (!blockEntity && !ResourceLocation.isValidResourceLocation( settings.host.entityType)) { @@ -1700,12 +1728,37 @@ class Validator { }); if (Validator.#isFiniteNumber(ctx.animationCount) && ctx.animationCount > Validator.BUDGETS.maxAnimationCount) { - warnings.push({ + errors.push({ code: 'HIGH_ANIMATION_COUNT', - message: `More than ${Validator.BUDGETS.maxAnimationCount} animations` + message: `More than ${Validator.BUDGETS.maxAnimationCount} animations; the mod refuses to load the model` }); } + (ctx.animations || []).forEach((animation) => { + const name = String(animation.name || '').toLowerCase(); + if (!ANIMATION_CLIPS.includes(name)) { + warnings.push({ + code: 'NON_STANDARD_ANIMATION', + message: `Animation "${animation.name}" is not one of ${ANIMATION_CLIPS.join( + ', ')} and is ignored by the mod` + }); + } + if (animation.hasExpression) { + warnings.push({ + code: 'UNSUPPORTED_ANIMATION_EXPRESSION', + message: `Animation "${animation.name}" uses expressions; the mod only supports numeric keyframes and drops the clip` + }); + } + (animation.channels || []).forEach((channel) => { + if (!SUPPORTED_ANIMATION_CHANNELS.has(channel)) { + warnings.push({ + code: 'UNSUPPORTED_ANIMATION_CHANNEL', + message: `Animation "${animation.name}" uses the "${channel}" channel which is ignored by the mod` + }); + } + }); + }); + Validator.getMissingBodyParts(settings.host.bodyType, ctx.boneNames || []).forEach((part) => { warnings.push({ @@ -1922,6 +1975,7 @@ const BODY_TYPES = [ const MOVEMENT_TYPES = ['ground', 'water', 'amphibious', 'static']; const BEHAVIOR_MODES = ['idle_only', 'ambient', 'static', 'external_owner']; const ANIMATION_MODES = ['automatic', 'random_idle', 'none']; +const ANIMATION_CLIPS = ['idle', 'walk', 'swim', 'fly']; const GAIT_TYPES = ['natural', 'feline', 'ungulate']; const GROUND_ENTITY = 'easy_model_entities:ground_entity'; @@ -1932,6 +1986,9 @@ const AMPHIBIOUS_ENTITY = 'easy_model_entities:amphibious_entity'; const FALLBACK_DIMENSIONS = {width: 0.6, height: 1.8, eyeHeight: 1.62}; const BLOCK_ENTITY_DIMENSIONS = {width: 1.0, height: 1.0, eyeHeight: 0.5}; +const DEFAULT_MAX_HEALTH = 10; +const DEFAULT_FOLLOW_RANGE = 16; + const PRESET_TYPES = [ 'custom', 'static', @@ -2217,7 +2274,11 @@ function blockEntityPresetDefaults(presetType) { dimensions: {...BLOCK_ENTITY_DIMENSIONS}, movement: {speed: 0, stepHeight: 0, gravity: false}, behavior: {mode: 'static', lookAtPlayers: false, randomStroll: false}, - attributes: {maxHealth: 10, movementSpeed: 0, followRange: 16}, + attributes: { + maxHealth: DEFAULT_MAX_HEALTH, + movementSpeed: 0, + followRange: DEFAULT_FOLLOW_RANGE + }, rendering: defaultRenderingSettings('cuboid_still'), animation: defaultAnimationSettings(blockEntityAnimationMode(presetType)) }; @@ -2253,9 +2314,9 @@ function presetDefaults(presetType, modelType) { randomStroll: wandersByMovement(move) && mode === 'ambient' }, attributes: { - maxHealth: 10, + maxHealth: DEFAULT_MAX_HEALTH, movementSpeed: movement.speed, - followRange: 16 + followRange: DEFAULT_FOLLOW_RANGE }, rendering: defaultRenderingSettings(presetType), animation: defaultAnimationSettings(animationMode(presetType)) @@ -2269,10 +2330,13 @@ module.exports = { MODEL_TYPES, BODY_TYPES, MOVEMENT_TYPES, + DEFAULT_MAX_HEALTH, + DEFAULT_FOLLOW_RANGE, BEHAVIOR_MODES, PRESET_TYPES, BLOCK_ENTITY_PRESET_TYPES, ANIMATION_MODES, + ANIMATION_CLIPS, GAIT_TYPES, SELECTABLE_PRESET_TYPES, isStablePreset, @@ -2284,7 +2348,9 @@ module.exports = { animationMode, presetDimensions, presetShadowRadius, - presetDefaults + presetDefaults, + defaultRenderingSettings, + defaultAnimationSettings }; @@ -2312,12 +2378,7 @@ module.exports = { * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const { - PRESET_TYPES, - SELECTABLE_PRESET_TYPES, - MODEL_TYPE_ENTITY, - presetDefaults -} = __webpack_require__(151); +const {MODEL_TYPE_ENTITY, presetDefaults} = __webpack_require__(151); const {getDefaultVersionId} = __webpack_require__(954); const DEFAULT_PRESET = 'statue'; @@ -2387,8 +2448,6 @@ function applyTemplate(presetType, modelType) { module.exports = { DEFAULT_PRESET, - PRESET_TYPES, - SELECTABLE_PRESET_TYPES, getDefaults, applyTemplate, deepMerge, @@ -2461,10 +2520,6 @@ function getVersion(id) { return VERSIONS.find((version) => version.id === id) || null; } -function getEnabledVersions() { - return getVersions().filter((version) => version.enabled); -} - function getDefaultVersionId() { return DEFAULT_VERSION_ID; } @@ -2482,7 +2537,6 @@ function getPackFormats(id) { module.exports = { getVersions, - getEnabledVersions, getDefaultVersionId, getPackFormats }; @@ -2512,13 +2566,10 @@ module.exports = { * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const {getDefaults, applyTemplate, deepMerge} = __webpack_require__(668); const { MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY } = __webpack_require__(151); -const {ModelDimensions} = __webpack_require__(763); -const {VisibleBounds} = __webpack_require__(508); const {t} = __webpack_require__(16); const { presetOptions, @@ -2532,151 +2583,13 @@ const { animationModeOptions, versionOptions } = __webpack_require__(664); - -function settingsToForm(settings) { - const offset = settings.rendering.visibleBoundsOffset || [0, 0, 0]; - - return { - namespace: settings.namespace, - profileId: settings.profileId, - targetVersion: settings.targetVersion, - hostEntityType: settings.host.entityType, - movementType: settings.host.movementType, - bodyType: settings.host.bodyType, - width: settings.dimensions.width, - height: settings.dimensions.height, - eyeHeight: settings.dimensions.eyeHeight, - speed: settings.movement.speed, - stepHeight: settings.movement.stepHeight, - gravity: settings.movement.gravity, - behaviorMode: settings.behavior.mode, - maxHealth: settings.attributes.maxHealth, - movementSpeed: settings.attributes.movementSpeed, - followRange: settings.attributes.followRange, - scale: settings.rendering.scale, - shadowRadius: settings.rendering.shadowRadius, - visibleBoundsWidth: settings.rendering.visibleBoundsWidth ?? 0, - visibleBoundsHeight: settings.rendering.visibleBoundsHeight ?? 0, - visibleBoundsOffsetX: offset[0], - visibleBoundsOffsetY: offset[1], - visibleBoundsOffsetZ: offset[2], - animationMode: settings.animation.mode, - swingSpeed: settings.animation.swingSpeed, - walkSpeedMultiplier: settings.animation.walkSpeedMultiplier, - idleStrength: settings.animation.idleStrength ?? 1, - gait: settings.animation.gait || 'natural' - }; -} - -function formToSettings(form, base) { - return { - schemaVersion: base.schemaVersion, - modelType: base.modelType, - presetType: base.presetType, - namespace: String(form.namespace || '').trim(), - profileId: String(form.profileId || '').trim(), - targetVersion: base.targetVersion, - host: { - entityType: form.hostEntityType, - movementType: form.movementType, - bodyType: form.bodyType - }, - dimensions: { - width: Number(form.width), - height: Number(form.height), - eyeHeight: Number(form.eyeHeight) - }, - movement: { - speed: Number(form.speed), - stepHeight: Number(form.stepHeight), - gravity: !!form.gravity - }, - behavior: { - mode: form.behaviorMode, - lookAtPlayers: base.behavior ? base.behavior.lookAtPlayers : true, - randomStroll: base.behavior ? base.behavior.randomStroll : false - }, - attributes: { - maxHealth: Number(form.maxHealth), - movementSpeed: Number(form.movementSpeed), - followRange: Number(form.followRange) - }, - rendering: { - scale: Number(form.scale), - shadowRadius: Number(form.shadowRadius), - visibleBoundsWidth: Number(form.visibleBoundsWidth), - visibleBoundsHeight: Number(form.visibleBoundsHeight), - visibleBoundsOffset: [ - Number(form.visibleBoundsOffsetX), - Number(form.visibleBoundsOffsetY), - Number(form.visibleBoundsOffsetZ) - ] - }, - animation: { - mode: form.animationMode, - swingSpeed: Number(form.swingSpeed), - walkSpeedMultiplier: Number(form.walkSpeedMultiplier), - idleStrength: Number(form.idleStrength), - gait: form.gait - } - }; -} - -function activeModelType(form) { - return form.exportType === 'model_only' - ? MODEL_TYPE_ENTITY : (form.modelType || MODEL_TYPE_ENTITY); -} - -function activePreset(form) { - return activeModelType(form) === MODEL_TYPE_BLOCK_ENTITY - ? (form.blockPreset || 'static') : (form.preset || 'custom'); -} - -function presetFormValues(presetType, modelType, modelDimensions, - visibleBounds) { - const settings = ModelDimensions.applyModelDimensions( - applyTemplate(presetType, modelType), modelDimensions); - VisibleBounds.applyVisibleBounds(settings, visibleBounds); - const form = settingsToForm(settings); - delete form.namespace; - delete form.profileId; - delete form.targetVersion; - - return form; -} - -function resolveExportSettings(form, base, modelDimensions, visibleBounds) { - const exportType = form.exportType || 'packs'; - const modelType = activeModelType(form); - const preset = activePreset(form); - - let settings; - if (modelType !== MODEL_TYPE_BLOCK_ENTITY && preset === 'custom') { - settings = deepMerge(getDefaults(), base); - } else { - settings = ModelDimensions.applyModelDimensions( - applyTemplate(preset, modelType), modelDimensions); - VisibleBounds.applyVisibleBounds(settings, visibleBounds); - } - - settings.schemaVersion = base.schemaVersion || settings.schemaVersion; - settings.modelType = modelType; - settings.presetType = preset; - settings.namespace = String(form.namespace || '').trim(); - settings.profileId = String(form.profileId || '').trim(); - settings.targetVersion = form.targetVersion; - - if (form.customize) { - settings = formToSettings(form, settings); - } - // Remembered so a customized project reopens with its values (and target). - settings.customize = !!form.customize; - settings.exportType = exportType; - settings.exportTarget = exportType === 'packs' ? 'packs' : 'mod_project'; - settings.modelOnly = exportType === 'model_only'; - - return settings; -} +const { + settingsToForm, + activeModelType, + activePreset, + presetFormValues, + resolveExportSettings +} = __webpack_require__(807); function advancedField(field, showCustomize) { return Object.assign({condition: showCustomize}, field); @@ -2691,11 +2604,8 @@ function buildFormConfig(settings, ui) { const blockEntitySelected = (form) => standalone(form) && form.modelType === MODEL_TYPE_BLOCK_ENTITY; const customizeOn = (form) => allowCustomize && !!form.customize; - // Server-profile advanced fields only make sense for a standalone datapack. const showServer = (form) => customizeOn(form) && standalone(form); - // Host/movement/behavior/attributes only apply to standalone entities. const showEntity = (form) => showServer(form) && !blockEntitySelected(form); - // Render-profile advanced fields apply to every export kind. const showRender = customizeOn; const experimental = !!state.experimental; @@ -2952,7 +2862,6 @@ function openExportDialog(options) { onConfirm(form) { const finalSettings = resolveExportSettings(form, settings, modelDimensions, visibleBounds); - // Records that experimental presets were available for this export. finalSettings.experimental = !!options.experimental; options.onExport(finalSettings, finalSettings.exportTarget); } @@ -2963,11 +2872,6 @@ function openExportDialog(options) { } module.exports = { - presetOptions, - settingsToForm, - formToSettings, - presetFormValues, - resolveExportSettings, openExportDialog }; @@ -3000,12 +2904,26 @@ const {getVersions} = __webpack_require__(954); const { SELECTABLE_PRESET_TYPES, BLOCK_ENTITY_PRESET_TYPES, + MOVEMENT_TYPES, + BODY_TYPES, + GAIT_TYPES, + BEHAVIOR_MODES, + ANIMATION_MODES, isStablePreset, MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY } = __webpack_require__(151); const {t} = __webpack_require__(16); +function optionsFromEnum(values, prefix) { + const options = {}; + values.forEach((id) => { + options[id] = t(`${prefix}.${id}`); + }); + + return options; +} + function presetLabel(id) { return id === 'custom' ? t('eme.preset.custom') : t(`eme.preset.${id}`); } @@ -3018,11 +2936,10 @@ function entityPresetIds(experimental) { function blockPresetIds(experimental) { return experimental - ? BLOCK_ENTITY_PRESET_TYPES.slice() + ? [...BLOCK_ENTITY_PRESET_TYPES] : BLOCK_ENTITY_PRESET_TYPES.filter(isStablePreset); } -// Experimental (WIP) presets are hidden unless the experimental setting is on. function presetOptions(modelType, experimental, ensure) { const ids = modelType === MODEL_TYPE_BLOCK_ENTITY ? blockPresetIds(experimental) : entityPresetIds(experimental); @@ -3062,52 +2979,23 @@ function hostEntityTypeOptions() { } function movementTypeOptions() { - return { - ground: t('eme.movement.ground'), - water: t('eme.movement.water'), - amphibious: t('eme.movement.amphibious'), - static: t('eme.movement.static') - }; + return optionsFromEnum(MOVEMENT_TYPES, 'eme.movement'); } function bodyTypeOptions() { - return { - static: t('eme.body.static'), - biped: t('eme.body.biped'), - quadruped: t('eme.body.quadruped'), - aquatic: t('eme.body.aquatic'), - amphibious: t('eme.body.amphibious'), - winged: t('eme.body.winged'), - winged_humanoid: t('eme.body.winged_humanoid'), - arthropod: t('eme.body.arthropod'), - cuboid: t('eme.body.cuboid'), - floating: t('eme.body.floating') - }; + return optionsFromEnum(BODY_TYPES, 'eme.body'); } function gaitOptions() { - return { - natural: t('eme.gait.natural'), - feline: t('eme.gait.feline'), - ungulate: t('eme.gait.ungulate') - }; + return optionsFromEnum(GAIT_TYPES, 'eme.gait'); } function behaviorModeOptions() { - return { - idle_only: t('eme.behavior.idle_only'), - ambient: t('eme.behavior.ambient'), - static: t('eme.behavior.static'), - external_owner: t('eme.behavior.external_owner') - }; + return optionsFromEnum(BEHAVIOR_MODES, 'eme.behavior'); } function animationModeOptions() { - return { - automatic: t('eme.animation.automatic'), - random_idle: t('eme.animation.random_idle'), - none: t('eme.animation.none') - }; + return optionsFromEnum(ANIMATION_MODES, 'eme.animation'); } function versionOptions() { @@ -3134,6 +3022,192 @@ module.exports = { }; +/***/ }, + +/***/ 807 +(module, __unused_webpack_exports, __webpack_require__) { + +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {getDefaults, applyTemplate, deepMerge} = __webpack_require__(668); +const { + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY +} = __webpack_require__(151); +const {ModelDimensions} = __webpack_require__(763); +const {VisibleBounds} = __webpack_require__(508); + +function settingsToForm(settings) { + const offset = settings.rendering.visibleBoundsOffset || [0, 0, 0]; + + return { + namespace: settings.namespace, + profileId: settings.profileId, + targetVersion: settings.targetVersion, + hostEntityType: settings.host.entityType, + movementType: settings.host.movementType, + bodyType: settings.host.bodyType, + width: settings.dimensions.width, + height: settings.dimensions.height, + eyeHeight: settings.dimensions.eyeHeight, + speed: settings.movement.speed, + stepHeight: settings.movement.stepHeight, + gravity: settings.movement.gravity, + behaviorMode: settings.behavior.mode, + maxHealth: settings.attributes.maxHealth, + movementSpeed: settings.attributes.movementSpeed, + followRange: settings.attributes.followRange, + scale: settings.rendering.scale, + shadowRadius: settings.rendering.shadowRadius, + visibleBoundsWidth: settings.rendering.visibleBoundsWidth ?? 0, + visibleBoundsHeight: settings.rendering.visibleBoundsHeight ?? 0, + visibleBoundsOffsetX: offset[0], + visibleBoundsOffsetY: offset[1], + visibleBoundsOffsetZ: offset[2], + animationMode: settings.animation.mode, + swingSpeed: settings.animation.swingSpeed, + walkSpeedMultiplier: settings.animation.walkSpeedMultiplier, + idleStrength: settings.animation.idleStrength ?? 1, + gait: settings.animation.gait || 'natural' + }; +} + +function formToSettings(form, base) { + return { + schemaVersion: base.schemaVersion, + modelType: base.modelType, + presetType: base.presetType, + namespace: String(form.namespace || '').trim(), + profileId: String(form.profileId || '').trim(), + targetVersion: base.targetVersion, + host: { + entityType: form.hostEntityType, + movementType: form.movementType, + bodyType: form.bodyType + }, + dimensions: { + width: Number(form.width), + height: Number(form.height), + eyeHeight: Number(form.eyeHeight) + }, + movement: { + speed: Number(form.speed), + stepHeight: Number(form.stepHeight), + gravity: !!form.gravity + }, + behavior: { + mode: form.behaviorMode, + lookAtPlayers: base.behavior ? base.behavior.lookAtPlayers : true, + randomStroll: base.behavior ? base.behavior.randomStroll : false + }, + attributes: { + maxHealth: Number(form.maxHealth), + movementSpeed: Number(form.movementSpeed), + followRange: Number(form.followRange) + }, + rendering: { + scale: Number(form.scale), + shadowRadius: Number(form.shadowRadius), + visibleBoundsWidth: Number(form.visibleBoundsWidth), + visibleBoundsHeight: Number(form.visibleBoundsHeight), + visibleBoundsOffset: [ + Number(form.visibleBoundsOffsetX), + Number(form.visibleBoundsOffsetY), + Number(form.visibleBoundsOffsetZ) + ] + }, + animation: { + mode: form.animationMode, + swingSpeed: Number(form.swingSpeed), + walkSpeedMultiplier: Number(form.walkSpeedMultiplier), + idleStrength: Number(form.idleStrength), + gait: form.gait + } + }; +} + +function activeModelType(form) { + return form.exportType === 'model_only' + ? MODEL_TYPE_ENTITY : (form.modelType || MODEL_TYPE_ENTITY); +} + +function activePreset(form) { + return activeModelType(form) === MODEL_TYPE_BLOCK_ENTITY + ? (form.blockPreset || 'static') : (form.preset || 'custom'); +} + +function presetFormValues(presetType, modelType, modelDimensions, + visibleBounds) { + const settings = ModelDimensions.applyModelDimensions( + applyTemplate(presetType, modelType), modelDimensions); + VisibleBounds.applyVisibleBounds(settings, visibleBounds); + const form = settingsToForm(settings); + delete form.namespace; + delete form.profileId; + delete form.targetVersion; + + return form; +} + +function resolveExportSettings(form, base, modelDimensions, visibleBounds) { + const exportType = form.exportType || 'packs'; + const modelType = activeModelType(form); + const preset = activePreset(form); + + let settings; + if (modelType !== MODEL_TYPE_BLOCK_ENTITY && preset === 'custom') { + settings = deepMerge(getDefaults(), base); + } else { + settings = ModelDimensions.applyModelDimensions( + applyTemplate(preset, modelType), modelDimensions); + VisibleBounds.applyVisibleBounds(settings, visibleBounds); + } + + settings.schemaVersion = base.schemaVersion || settings.schemaVersion; + settings.modelType = modelType; + settings.presetType = preset; + settings.namespace = String(form.namespace || '').trim(); + settings.profileId = String(form.profileId || '').trim(); + settings.targetVersion = form.targetVersion; + + if (form.customize) { + settings = formToSettings(form, settings); + } + settings.customize = !!form.customize; + settings.exportType = exportType; + settings.exportTarget = exportType === 'packs' ? 'packs' : 'mod_project'; + settings.modelOnly = exportType === 'model_only'; + + return settings; +} + +module.exports = { + settingsToForm, + formToSettings, + activeModelType, + activePreset, + presetFormValues, + resolveExportSettings +}; + + /***/ }, /***/ 317 @@ -3652,14 +3726,11 @@ function formatIssues(issues) { return issues.map((issue) => `• ${issue.message}`).join('\n'); } -function collectContext() { - return Object.assign({}, BlockbenchAdapter.getModelStats()); -} - function runExport(settings, target) { const textureResolution = resolveTextures( BlockbenchAdapter.collectTextures(), settings); - const result = Validator.validateSettings(settings, collectContext()); + const result = Validator.validateSettings(settings, + BlockbenchAdapter.getModelStats()); if (!result.valid) { Blockbench.showMessageBox({ @@ -3790,7 +3861,7 @@ BBPlugin.register('easy_model_entities', { author: 'Markus Bordihn', description: 'Export Blockbench models for the Easy Model Entities mod (Minecraft: Java Edition) as ready-to-install Data Pack and Resource Pack files.', tags: ['Minecraft: Java Edition', 'Format', 'Exporter', 'Entity'], - version: '0.1.0', + version: '1.0.0', min_version: '4.9.0', variant: 'desktop', await_loading: true, diff --git a/plugins/easy_model_entities/src/BlockbenchAdapter.js b/plugins/easy_model_entities/src/BlockbenchAdapter.js index 1bfb4129..f2309783 100644 --- a/plugins/easy_model_entities/src/BlockbenchAdapter.js +++ b/plugins/easy_model_entities/src/BlockbenchAdapter.js @@ -17,6 +17,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +const {Validator} = require('./model/Validator'); + class BlockbenchAdapter { static PROJECT_PROPERTY = 'eme_export'; @@ -62,6 +64,43 @@ class BlockbenchAdapter { })); } + static #isNumericValue(value) { + if (typeof value === 'number') { + return Number.isFinite(value); + } + + return typeof value === 'string' && value.trim() !== '' + && Number.isFinite(Number(value)); + } + + static #summarizeAnimation(animation) { + const channels = new Set(); + let hasExpression = false; + + Object.values(animation.animators || {}).forEach((animator) => { + (animator.keyframes || []).forEach((keyframe) => { + if (keyframe.channel) { + channels.add(keyframe.channel); + } + (keyframe.data_points || []).forEach((point) => { + ['x', 'y', 'z'].forEach((axis) => { + const value = point?.[axis]; + if (value !== undefined && value !== null + && !BlockbenchAdapter.#isNumericValue(value)) { + hasExpression = true; + } + }); + }); + }); + }); + + return { + name: animation.name || '', + channels: [...channels], + hasExpression: hasExpression + }; + } + static getModelStats() { const cubes = typeof Cube !== 'undefined' && Cube.all ? Cube.all : []; const groups = typeof Group !== 'undefined' && Group.all ? Group.all : []; @@ -88,6 +127,8 @@ class BlockbenchAdapter { cubeCount: cubes.length, boneCount: groups.length, animationCount: animations.length, + animations: animations.map( + (animation) => BlockbenchAdapter.#summarizeAnimation(animation)), hierarchyDepth: maxDepth, boneNames: groups.map((group) => group.name), textureWidth: texture ? texture.width : undefined, @@ -227,6 +268,10 @@ class BlockbenchAdapter { const fs = require('fs'); const path = require('path'); files.forEach((file) => { + const result = Validator.validateOutputPath(rootDir, file.path); + if (!result.valid) { + throw new Error(`${result.code}: ${result.message} (${file.path})`); + } const fullPath = path.join(rootDir, file.path); fs.mkdirSync(path.dirname(fullPath), {recursive: true}); const data = diff --git a/plugins/easy_model_entities/src/builders/renderProfile.js b/plugins/easy_model_entities/src/builders/renderProfile.js index 9e305249..04f4d584 100644 --- a/plugins/easy_model_entities/src/builders/renderProfile.js +++ b/plugins/easy_model_entities/src/builders/renderProfile.js @@ -20,7 +20,8 @@ const { bodyType, animationMode, - presetShadowRadius, + defaultRenderingSettings, + defaultAnimationSettings, isCustom, MODEL_TYPE_BLOCK_ENTITY } = require('../model/presetTypes'); @@ -31,40 +32,37 @@ function renderPresetType(settings) { ? 'static' : settings.presetType; } +function toRenderingJson(values) { + return { + scale: values.scale, + shadow_radius: values.shadowRadius, + visible_bounds_width: values.visibleBoundsWidth ?? 0, + visible_bounds_height: values.visibleBoundsHeight ?? 0, + visible_bounds_offset: values.visibleBoundsOffset || [0, 0, 0] + }; +} + function buildRendering(settings) { - const rendering = settings.rendering; - return diffFlat({ - scale: rendering.scale, - shadow_radius: rendering.shadowRadius, - visible_bounds_width: rendering.visibleBoundsWidth ?? 0, - visible_bounds_height: rendering.visibleBoundsHeight ?? 0, - visible_bounds_offset: rendering.visibleBoundsOffset || [0, 0, 0] - }, { - scale: 1, - shadow_radius: presetShadowRadius(renderPresetType(settings)), - visible_bounds_width: 0, - visible_bounds_height: 0, - visible_bounds_offset: [0, 0, 0] - }); + return diffFlat(toRenderingJson(settings.rendering), + toRenderingJson(defaultRenderingSettings(renderPresetType(settings)))); +} + +function toAnimationJson(values) { + return { + mode: values.mode, + swing_speed: values.swingSpeed, + walk_speed_multiplier: values.walkSpeedMultiplier, + idle_strength: values.idleStrength ?? 1, + gait: values.gait || 'natural' + }; } function buildAnimation(settings) { - const defaultMode = animationMode(renderPresetType(settings)); - const animation = diffFlat({ - mode: settings.animation.mode, - swing_speed: settings.animation.swingSpeed, - walk_speed_multiplier: settings.animation.walkSpeedMultiplier, - idle_strength: settings.animation.idleStrength ?? 1, - gait: settings.animation.gait || 'natural' - }, { - mode: defaultMode, - swing_speed: 1, - walk_speed_multiplier: 1, - idle_strength: 1, - gait: 'natural' - }); + const defaults = defaultAnimationSettings( + animationMode(renderPresetType(settings))); + const animation = diffFlat(toAnimationJson(settings.animation), + toAnimationJson(defaults)); - // Animation timing is meaningless when animation is disabled. if (settings.animation.mode === 'none') { delete animation.swing_speed; delete animation.walk_speed_multiplier; diff --git a/plugins/easy_model_entities/src/builders/serverProfile.js b/plugins/easy_model_entities/src/builders/serverProfile.js index 42726acc..6eeea23f 100644 --- a/plugins/easy_model_entities/src/builders/serverProfile.js +++ b/plugins/easy_model_entities/src/builders/serverProfile.js @@ -24,14 +24,13 @@ const { behaviorModeFor, wandersByMovement, isCustom, + DEFAULT_MAX_HEALTH, + DEFAULT_FOLLOW_RANGE, MODEL_TYPE_BLOCK_ENTITY, MODEL_TYPE_ENTITY } = require('../model/presetTypes'); const {diffFlat, assignIfPresent} = require('./profileDiff'); -const DEFAULT_MAX_HEALTH = 10; -const DEFAULT_FOLLOW_RANGE = 16; - function effectiveDefaults(settings) { const preset = settings.presetType; const movementType = settings.host.movementType; @@ -60,9 +59,6 @@ function effectiveDefaults(settings) { }; } -// Entity host settings. The mod derives type/movement_type/body_type from the -// preset, so only the "custom" preset needs to spell them out; non-custom -// presets emit nothing here. function buildEntity(settings, custom) { if (!custom) { return {}; @@ -106,9 +102,6 @@ function buildServerProfile(settings) { preset_type: settings.presetType }; - // The server profile lives in a "/" subfolder, so its derived - // id differs from the flat render profile id. The render profile must be named - // explicitly or the mod would look for "/" instead. profile.client = { render_profile: `${settings.namespace}:${settings.profileId}` }; diff --git a/plugins/easy_model_entities/src/format/EmeFormat.js b/plugins/easy_model_entities/src/format/EmeFormat.js index 25d34d99..ca7d2f1d 100644 --- a/plugins/easy_model_entities/src/format/EmeFormat.js +++ b/plugins/easy_model_entities/src/format/EmeFormat.js @@ -57,9 +57,7 @@ function registerEmeFormat(icon) { if (Project && !Project.model_identifier && Project.name) { Project.model_identifier = toModelIdentifier(Project.name); } - }, - onDeactivation() { - }, + } }); } diff --git a/plugins/easy_model_entities/src/index.js b/plugins/easy_model_entities/src/index.js index 608bbb5a..c32684d3 100644 --- a/plugins/easy_model_entities/src/index.js +++ b/plugins/easy_model_entities/src/index.js @@ -129,14 +129,11 @@ function formatIssues(issues) { return issues.map((issue) => `• ${issue.message}`).join('\n'); } -function collectContext() { - return Object.assign({}, BlockbenchAdapter.getModelStats()); -} - function runExport(settings, target) { const textureResolution = resolveTextures( BlockbenchAdapter.collectTextures(), settings); - const result = Validator.validateSettings(settings, collectContext()); + const result = Validator.validateSettings(settings, + BlockbenchAdapter.getModelStats()); if (!result.valid) { Blockbench.showMessageBox({ @@ -267,7 +264,7 @@ BBPlugin.register('easy_model_entities', { author: 'Markus Bordihn', description: 'Export Blockbench models for the Easy Model Entities mod (Minecraft: Java Edition) as ready-to-install Data Pack and Resource Pack files.', tags: ['Minecraft: Java Edition', 'Format', 'Exporter', 'Entity'], - version: '0.1.0', + version: '1.0.0', min_version: '4.9.0', variant: 'desktop', await_loading: true, diff --git a/plugins/easy_model_entities/src/model/ModelDimensions.js b/plugins/easy_model_entities/src/model/ModelDimensions.js index 6b26282f..d896c6f6 100644 --- a/plugins/easy_model_entities/src/model/ModelDimensions.js +++ b/plugins/easy_model_entities/src/model/ModelDimensions.js @@ -27,9 +27,8 @@ class ModelDimensions { static #EYE_HEIGHT_FACTOR = {biped: 0.9, quadruped: 0.8, static: 0.85}; - static #round(value, digits) { - const factor = Math.pow(10, digits == null ? 3 : digits); - return Math.round(value * factor) / factor; + static #round(value) { + return Math.round(value * 1000) / 1000; } static #isUsableBounds(bounds) { @@ -67,7 +66,6 @@ class ModelDimensions { }; } - // mutates settings in-place and returns it static applyModelDimensions(settings, modelDimensions) { if (!modelDimensions) { return settings; diff --git a/plugins/easy_model_entities/src/model/PresetDetector.js b/plugins/easy_model_entities/src/model/PresetDetector.js index e93ecc7b..46a81c32 100644 --- a/plugins/easy_model_entities/src/model/PresetDetector.js +++ b/plugins/easy_model_entities/src/model/PresetDetector.js @@ -17,11 +17,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Suggests the most likely ModelPresetType from a model's bone names and bounds. -// Pure and side-effect free so it can be unit tested in isolation. Always -// suggests a "still" variant (the safe default); the user can switch to a -// moving variant in the dialog. - class PresetDetector { static #CUBE_RATIO = 1.4; diff --git a/plugins/easy_model_entities/src/model/Validator.js b/plugins/easy_model_entities/src/model/Validator.js index c6765708..bc10fb62 100644 --- a/plugins/easy_model_entities/src/model/Validator.js +++ b/plugins/easy_model_entities/src/model/Validator.js @@ -21,9 +21,12 @@ const {ResourceLocation} = require('../utils/ResourceLocation'); const { PRESET_TYPES, BLOCK_ENTITY_PRESET_TYPES, + ANIMATION_CLIPS, MODEL_TYPE_BLOCK_ENTITY } = require('./presetTypes'); +const SUPPORTED_ANIMATION_CHANNELS = new Set(['rotation', 'position']); + class Validator { static BUDGETS = { maxTextureSize: 2048, @@ -105,8 +108,6 @@ class Validator { }); } const blockEntity = settings.modelType === MODEL_TYPE_BLOCK_ENTITY; - - // Block entity host type is derived by the mod; only entities carry one. if (!blockEntity && !ResourceLocation.isValidResourceLocation( settings.host.entityType)) { @@ -224,12 +225,37 @@ class Validator { }); if (Validator.#isFiniteNumber(ctx.animationCount) && ctx.animationCount > Validator.BUDGETS.maxAnimationCount) { - warnings.push({ + errors.push({ code: 'HIGH_ANIMATION_COUNT', - message: `More than ${Validator.BUDGETS.maxAnimationCount} animations` + message: `More than ${Validator.BUDGETS.maxAnimationCount} animations; the mod refuses to load the model` }); } + (ctx.animations || []).forEach((animation) => { + const name = String(animation.name || '').toLowerCase(); + if (!ANIMATION_CLIPS.includes(name)) { + warnings.push({ + code: 'NON_STANDARD_ANIMATION', + message: `Animation "${animation.name}" is not one of ${ANIMATION_CLIPS.join( + ', ')} and is ignored by the mod` + }); + } + if (animation.hasExpression) { + warnings.push({ + code: 'UNSUPPORTED_ANIMATION_EXPRESSION', + message: `Animation "${animation.name}" uses expressions; the mod only supports numeric keyframes and drops the clip` + }); + } + (animation.channels || []).forEach((channel) => { + if (!SUPPORTED_ANIMATION_CHANNELS.has(channel)) { + warnings.push({ + code: 'UNSUPPORTED_ANIMATION_CHANNEL', + message: `Animation "${animation.name}" uses the "${channel}" channel which is ignored by the mod` + }); + } + }); + }); + Validator.getMissingBodyParts(settings.host.bodyType, ctx.boneNames || []).forEach((part) => { warnings.push({ diff --git a/plugins/easy_model_entities/src/model/presetTypes.js b/plugins/easy_model_entities/src/model/presetTypes.js index dea053c1..4f765893 100644 --- a/plugins/easy_model_entities/src/model/presetTypes.js +++ b/plugins/easy_model_entities/src/model/presetTypes.js @@ -41,6 +41,7 @@ const BODY_TYPES = [ const MOVEMENT_TYPES = ['ground', 'water', 'amphibious', 'static']; const BEHAVIOR_MODES = ['idle_only', 'ambient', 'static', 'external_owner']; const ANIMATION_MODES = ['automatic', 'random_idle', 'none']; +const ANIMATION_CLIPS = ['idle', 'walk', 'swim', 'fly']; const GAIT_TYPES = ['natural', 'feline', 'ungulate']; const GROUND_ENTITY = 'easy_model_entities:ground_entity'; @@ -51,6 +52,9 @@ const AMPHIBIOUS_ENTITY = 'easy_model_entities:amphibious_entity'; const FALLBACK_DIMENSIONS = {width: 0.6, height: 1.8, eyeHeight: 1.62}; const BLOCK_ENTITY_DIMENSIONS = {width: 1.0, height: 1.0, eyeHeight: 0.5}; +const DEFAULT_MAX_HEALTH = 10; +const DEFAULT_FOLLOW_RANGE = 16; + const PRESET_TYPES = [ 'custom', 'static', @@ -336,7 +340,11 @@ function blockEntityPresetDefaults(presetType) { dimensions: {...BLOCK_ENTITY_DIMENSIONS}, movement: {speed: 0, stepHeight: 0, gravity: false}, behavior: {mode: 'static', lookAtPlayers: false, randomStroll: false}, - attributes: {maxHealth: 10, movementSpeed: 0, followRange: 16}, + attributes: { + maxHealth: DEFAULT_MAX_HEALTH, + movementSpeed: 0, + followRange: DEFAULT_FOLLOW_RANGE + }, rendering: defaultRenderingSettings('cuboid_still'), animation: defaultAnimationSettings(blockEntityAnimationMode(presetType)) }; @@ -372,9 +380,9 @@ function presetDefaults(presetType, modelType) { randomStroll: wandersByMovement(move) && mode === 'ambient' }, attributes: { - maxHealth: 10, + maxHealth: DEFAULT_MAX_HEALTH, movementSpeed: movement.speed, - followRange: 16 + followRange: DEFAULT_FOLLOW_RANGE }, rendering: defaultRenderingSettings(presetType), animation: defaultAnimationSettings(animationMode(presetType)) @@ -388,10 +396,13 @@ module.exports = { MODEL_TYPES, BODY_TYPES, MOVEMENT_TYPES, + DEFAULT_MAX_HEALTH, + DEFAULT_FOLLOW_RANGE, BEHAVIOR_MODES, PRESET_TYPES, BLOCK_ENTITY_PRESET_TYPES, ANIMATION_MODES, + ANIMATION_CLIPS, GAIT_TYPES, SELECTABLE_PRESET_TYPES, isStablePreset, @@ -403,5 +414,7 @@ module.exports = { animationMode, presetDimensions, presetShadowRadius, - presetDefaults + presetDefaults, + defaultRenderingSettings, + defaultAnimationSettings }; diff --git a/plugins/easy_model_entities/src/model/templates.js b/plugins/easy_model_entities/src/model/templates.js index d642de68..1c2af1df 100644 --- a/plugins/easy_model_entities/src/model/templates.js +++ b/plugins/easy_model_entities/src/model/templates.js @@ -17,12 +17,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const { - PRESET_TYPES, - SELECTABLE_PRESET_TYPES, - MODEL_TYPE_ENTITY, - presetDefaults -} = require('./presetTypes'); +const {MODEL_TYPE_ENTITY, presetDefaults} = require('./presetTypes'); const {getDefaultVersionId} = require('./versionMatrix'); const DEFAULT_PRESET = 'statue'; @@ -92,8 +87,6 @@ function applyTemplate(presetType, modelType) { module.exports = { DEFAULT_PRESET, - PRESET_TYPES, - SELECTABLE_PRESET_TYPES, getDefaults, applyTemplate, deepMerge, diff --git a/plugins/easy_model_entities/src/model/versionMatrix.js b/plugins/easy_model_entities/src/model/versionMatrix.js index 4882f2a9..ab180c50 100644 --- a/plugins/easy_model_entities/src/model/versionMatrix.js +++ b/plugins/easy_model_entities/src/model/versionMatrix.js @@ -58,10 +58,6 @@ function getVersion(id) { return VERSIONS.find((version) => version.id === id) || null; } -function getEnabledVersions() { - return getVersions().filter((version) => version.enabled); -} - function getDefaultVersionId() { return DEFAULT_VERSION_ID; } @@ -79,7 +75,6 @@ function getPackFormats(id) { module.exports = { getVersions, - getEnabledVersions, getDefaultVersionId, getPackFormats }; diff --git a/plugins/easy_model_entities/src/package-lock.json b/plugins/easy_model_entities/src/package-lock.json index 650438b4..d52b6f5d 100644 --- a/plugins/easy_model_entities/src/package-lock.json +++ b/plugins/easy_model_entities/src/package-lock.json @@ -1,12 +1,12 @@ { "name": "easy_model_entities", - "version": "0.1.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "easy_model_entities", - "version": "0.1.0", + "version": "1.0.0", "devDependencies": { "blockbench-types": "5.1.0", "copy-webpack-plugin": "14.0.0", @@ -1815,25 +1815,33 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" + "bin": { + "acorn": "bin/acorn" }, - "peerDependencies": { - "ajv": "^8.0.0" + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "peerDependencies": { + "acorn": "^8.14.0" } }, - "node_modules/ajv-formats/node_modules/ajv": { + "node_modules/ajv": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", @@ -1850,12 +1858,36 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } }, "node_modules/ansi-escapes": { "version": "4.3.2", @@ -2046,9 +2078,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.38", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", - "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", + "version": "2.10.41", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.41.tgz", + "integrity": "sha512-WwS7MHhqGHHlaVsqRZnhvCEMS0owDX+SxRlve7JkuH7My1Ara3ZriTmCQupPfYjxMZ8I/tgxtJYr2t7taHaH4A==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2161,9 +2193,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "version": "1.0.30001800", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", + "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", "dev": true, "funding": [ { @@ -2510,9 +2542,9 @@ "license": "MIT" }, "node_modules/electron": { - "version": "40.10.5", - "resolved": "https://registry.npmjs.org/electron/-/electron-40.10.5.tgz", - "integrity": "sha512-VzTIvwOYXZZufT9B83GDQogR1TFqREygRYhm0LE++QhGPjvBeg+W7siOP9K5+9rHMUnRuCX4YU/0ivLekN/UZQ==", + "version": "40.10.6", + "resolved": "https://registry.npmjs.org/electron/-/electron-40.10.6.tgz", + "integrity": "sha512-TGjlkOU9Lg6K4KjDbsErywCWCIDaNgLh0q+xj0nlpRoQhevI7VBIxBTtJI/V30lypyLAaXMpnP9O9jui1/qRFw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2529,9 +2561,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.378", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.378.tgz", - "integrity": "sha512-VinvOAuuPmdD1guEgGv5f2Qp7/vlfqOrUOMYNnOD4wj3pit8kRsQHzfIf6teyUGWo15Tg5+bOJaRunvyltpVWQ==", + "version": "1.5.385", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.385.tgz", + "integrity": "sha512-78sa/M08MNAYHQfjoWMvOlKQqZ0ElhSm/L5HNUc96VZ3b+KvDVnngFm8sYQy0XrhTRgAhggHr5abA7yTvRdo4Q==", "dev": true, "license": "ISC" }, @@ -2556,9 +2588,9 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.24.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.0.tgz", - "integrity": "sha512-SkE2t82KlkkxQRVMVLAGKxLfORGQfrkx5dkj+vlgXRVNEdPc4eZcR+J/Fvj8C+yKSFH5L0q3NFlyufOVQnCcYQ==", + "version": "5.24.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.1.tgz", + "integrity": "sha512-7DdUaTjmNwMcH2gLr1qycesKII3BK4RLy/mdAb7x10Lq7bR4aNKHt1BR1ZALSv0rPM/hF5wYF0PhGop/rJm8vw==", "dev": true, "license": "MIT", "dependencies": { @@ -2616,9 +2648,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", + "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", "dev": true, "license": "MIT" }, @@ -2787,9 +2819,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", "dev": true, "funding": [ { @@ -2813,6 +2845,24 @@ "bser": "2.1.1" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -3315,22 +3365,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-changed-files/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jest-circus": { "version": "30.4.2", "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", @@ -3363,22 +3397,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-circus/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/jest-cli": { "version": "30.4.2", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", @@ -3716,33 +3734,6 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runner/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/jest-runtime": { "version": "30.4.2", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", @@ -3920,9 +3911,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", "dev": true, "license": "MIT", "dependencies": { @@ -3953,6 +3944,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -4063,6 +4061,16 @@ "dev": true, "license": "MIT" }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -4170,9 +4178,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.49", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.49.tgz", - "integrity": "sha512-f06bl1D+8ZDkn2oOQQKAh5/otFWqVnM1Q5oerA8Pex7UfT66Tx4IPHIqVVFKqFT3FUtaDstdgkM7yT7JWhqxfw==", + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", "dev": true, "license": "MIT", "engines": { @@ -4229,16 +4237,16 @@ } }, "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4257,6 +4265,22 @@ "node": ">=8" } }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -4362,9 +4386,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -4398,9 +4422,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", "dev": true, "funding": [ { @@ -4606,43 +4630,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/semver": { "version": "7.8.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", @@ -4657,9 +4644,9 @@ } }, "node_modules/serialize-javascript": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.6.tgz", - "integrity": "sha512-ATTK5Q4gFVg0YDp1my2vqygyvhcklD/UV5GIlYHooGTn/NogJqIzpetkD6E5kmuVULqz/S9inUL25XcAgDRJQg==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.7.tgz", + "integrity": "sha512-YAy8Od6KV+uuwUuU50np8fGB/Aues6Y0nAhA9y/hId74PlKUcme4pXcBD46NWKr1Q4osN/iseZ17YqO1XfmI8g==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -4746,9 +4733,9 @@ } }, "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "license": "MIT", "dependencies": { @@ -5130,17 +5117,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/terser/node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, "node_modules/test-exclude": { @@ -5235,24 +5220,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -5564,42 +5531,6 @@ "node": ">=10.13.0" } }, - "node_modules/webpack/node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/webpack/node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, - "node_modules/webpack/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/plugins/easy_model_entities/src/package.json b/plugins/easy_model_entities/src/package.json index 99fbd589..2ff86bc5 100644 --- a/plugins/easy_model_entities/src/package.json +++ b/plugins/easy_model_entities/src/package.json @@ -1,6 +1,6 @@ { "name": "easy_model_entities", - "version": "0.1.0", + "version": "1.0.0", "description": "Export Blockbench projects into Easy Model Entities Data Pack and Resource Pack files.", "author": "Markus Bordihn", "private": true, diff --git a/plugins/easy_model_entities/src/tests/blockbenchAdapter.test.js b/plugins/easy_model_entities/src/tests/blockbenchAdapter.test.js new file mode 100644 index 00000000..97695663 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/blockbenchAdapter.test.js @@ -0,0 +1,54 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const fs = require('fs'); +const os = require('os'); +const path = require('path'); + +const {BlockbenchAdapter} = require('../BlockbenchAdapter'); + +describe('writeToDirectory', () => { + let rootDir; + + beforeEach(() => { + rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eme-test-')); + }); + + afterEach(() => { + fs.rmSync(rootDir, {recursive: true, force: true}); + }); + + test('writes files below the output root', () => { + BlockbenchAdapter.writeToDirectory(rootDir, + [{path: 'data/example/profile.json', content: '{}'}]); + expect(fs.readFileSync( + path.join(rootDir, 'data/example/profile.json'), 'utf8')).toBe('{}'); + }); + + test('rejects path traversal', () => { + expect(() => BlockbenchAdapter.writeToDirectory(rootDir, + [{path: '../escape.json', content: '{}'}])).toThrow('PATH_TRAVERSAL'); + expect(fs.existsSync(path.join(rootDir, '..', 'escape.json'))).toBe(false); + }); + + test('rejects absolute paths', () => { + expect(() => BlockbenchAdapter.writeToDirectory(rootDir, + [{path: 'C:/escape.json', content: '{}'}])).toThrow('ABSOLUTE_PATH'); + }); +}); diff --git a/plugins/easy_model_entities/src/tests/contractSync.test.js b/plugins/easy_model_entities/src/tests/contractSync.test.js index 59e279a4..d933a7c3 100644 --- a/plugins/easy_model_entities/src/tests/contractSync.test.js +++ b/plugins/easy_model_entities/src/tests/contractSync.test.js @@ -30,6 +30,7 @@ const { PRESET_TYPES, BLOCK_ENTITY_PRESET_TYPES, ANIMATION_MODES, + ANIMATION_CLIPS, GAIT_TYPES } = require('../model/presetTypes'); const {applyTemplate} = require('../model/templates'); @@ -55,6 +56,8 @@ describe('mod API contract sync', () => { sorted(contract.enums.block_entity_preset_types)); expect(sorted(ANIMATION_MODES)).toEqual( sorted(contract.enums.animation_modes)); + expect(sorted(ANIMATION_CLIPS)).toEqual( + sorted(contract.enums.animation_clips)); expect(sorted(GAIT_TYPES)).toEqual(sorted(contract.enums.gaits)); }); diff --git a/plugins/easy_model_entities/src/tests/exportDialog.test.js b/plugins/easy_model_entities/src/tests/exportDialog.test.js index c9fa5887..d961a40e 100644 --- a/plugins/easy_model_entities/src/tests/exportDialog.test.js +++ b/plugins/easy_model_entities/src/tests/exportDialog.test.js @@ -21,9 +21,9 @@ const { settingsToForm, formToSettings, presetFormValues, - resolveExportSettings, - presetOptions -} = require('../ui/exportDialog'); + resolveExportSettings +} = require('../ui/exportSettingsMapping'); +const {presetOptions} = require('../ui/exportDialogOptions'); const {fixtureSettings} = require('./fixtureData'); const MODEL_DIMS = {width: 1, height: 2, eyeHeight: 1.8}; diff --git a/plugins/easy_model_entities/src/tests/fixtureData.js b/plugins/easy_model_entities/src/tests/fixtureData.js index 142001a1..34f063a3 100644 --- a/plugins/easy_model_entities/src/tests/fixtureData.js +++ b/plugins/easy_model_entities/src/tests/fixtureData.js @@ -32,8 +32,6 @@ function fixtureSettings() { return settings; } -// A single custom texture at index 0, matching the legacy default path -// assets//textures/entity/.png. function fixtureTextureResolution(settings) { return resolveTextures([ { @@ -54,6 +52,5 @@ module.exports = { FIXTURE_MODEL_BYTES, FIXTURE_TEXTURE_BYTES, fixtureSettings, - fixtureTextureResolution, fixtureExportOptions }; diff --git a/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.1.0.json b/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.1.0.json index 79e6a832..2fb9610f 100644 --- a/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.1.0.json +++ b/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.1.0.json @@ -75,6 +75,12 @@ "random_idle", "none" ], + "animation_clips": [ + "idle", + "walk", + "swim", + "fly" + ], "gaits": [ "natural", "feline", diff --git a/plugins/easy_model_entities/src/tests/schemaConsistency.test.js b/plugins/easy_model_entities/src/tests/schemaConsistency.test.js index 13c02b53..886dd7f8 100644 --- a/plugins/easy_model_entities/src/tests/schemaConsistency.test.js +++ b/plugins/easy_model_entities/src/tests/schemaConsistency.test.js @@ -17,8 +17,8 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const {applyTemplate, deepMerge, SELECTABLE_PRESET_TYPES} = require( - '../model/templates'); +const {applyTemplate, deepMerge} = require('../model/templates'); +const {SELECTABLE_PRESET_TYPES} = require('../model/presetTypes'); const {buildServerProfile} = require('../builders/serverProfile'); const {buildRenderProfile} = require('../builders/renderProfile'); @@ -28,10 +28,6 @@ function withId(settings) { return settings; } -// If the plugin's preset defaults exactly mirror the mod's, an unmodified -// preset must reduce to just the model_type, preset_type and the required -// client.render_profile link (the mod fills in every other value from -// preset_type), proving the two stay in sync. describe('preset defaults mirror the mod (minimal output)', () => { describe.each(SELECTABLE_PRESET_TYPES)('%s', (preset) => { const settings = withId(applyTemplate(preset)); diff --git a/plugins/easy_model_entities/src/tests/templates.test.js b/plugins/easy_model_entities/src/tests/templates.test.js index 4d8cca34..c54dbf31 100644 --- a/plugins/easy_model_entities/src/tests/templates.test.js +++ b/plugins/easy_model_entities/src/tests/templates.test.js @@ -21,11 +21,13 @@ const { getDefaults, applyTemplate, DEFAULT_PRESET, - PRESET_TYPES, - SELECTABLE_PRESET_TYPES, deepMerge } = require('../model/templates'); -const {presetDefaults} = require('../model/presetTypes'); +const { + presetDefaults, + PRESET_TYPES, + SELECTABLE_PRESET_TYPES +} = require('../model/presetTypes'); describe('templates', () => { test('exposes the 20 mod preset types', () => { diff --git a/plugins/easy_model_entities/src/tests/validation.test.js b/plugins/easy_model_entities/src/tests/validation.test.js index 151ea4bf..b24b4d7e 100644 --- a/plugins/easy_model_entities/src/tests/validation.test.js +++ b/plugins/easy_model_entities/src/tests/validation.test.js @@ -101,15 +101,13 @@ describe('validateSettings', () => { cubeCount: 1000, boneCount: 200, hierarchyDepth: 50, - animationCount: 17, boneNames: QUADRUPED_BONES }); expect(result.valid).toBe(true); expect(codes(result.warnings)).toEqual( expect.arrayContaining( ['LARGE_TEXTURE', 'LARGE_MODEL', 'HIGH_CUBE_COUNT', - 'HIGH_BONE_COUNT', 'DEEP_HIERARCHY', - 'HIGH_ANIMATION_COUNT']) + 'HIGH_BONE_COUNT', 'DEEP_HIERARCHY']) ); }); @@ -135,6 +133,52 @@ describe('validateSettings', () => { expect(codes(result.warnings)).not.toContain('HIGH_ANIMATION_COUNT'); }); + test('rejects more animations than the mod accepts', () => { + const result = Validator.validateSettings(fixtureSettings(), { + hasModel: true, + hasTexture: true, + animationCount: 17, + boneNames: QUADRUPED_BONES + }); + expect(result.valid).toBe(false); + expect(codes(result.errors)).toContain('HIGH_ANIMATION_COUNT'); + }); + + test('standard animation clips produce no warnings', () => { + const result = Validator.validateSettings(fixtureSettings(), { + hasModel: true, + hasTexture: true, + animationCount: 2, + animations: [ + {name: 'idle', channels: ['rotation'], hasExpression: false}, + {name: 'WALK', channels: ['rotation', 'position'], hasExpression: false} + ], + boneNames: QUADRUPED_BONES + }); + expect(result.valid).toBe(true); + expect(result.warnings).toHaveLength(0); + }); + + test('warns about animations the mod ignores or drops', () => { + const result = Validator.validateSettings(fixtureSettings(), { + hasModel: true, + hasTexture: true, + animationCount: 3, + animations: [ + {name: 'attack', channels: ['rotation'], hasExpression: false}, + {name: 'walk', channels: ['rotation'], hasExpression: true}, + {name: 'idle', channels: ['rotation', 'scale'], hasExpression: false} + ], + boneNames: QUADRUPED_BONES + }); + expect(result.valid).toBe(true); + expect(codes(result.warnings)).toEqual( + expect.arrayContaining( + ['NON_STANDARD_ANIMATION', 'UNSUPPORTED_ANIMATION_EXPRESSION', + 'UNSUPPORTED_ANIMATION_CHANNEL']) + ); + }); + test('warns about missing body parts per body type', () => { const result = Validator.validateSettings(fixtureSettings(), {hasModel: true, hasTexture: true, boneNames: ['root', 'body']}); diff --git a/plugins/easy_model_entities/src/tests/versionMatrix.test.js b/plugins/easy_model_entities/src/tests/versionMatrix.test.js index 41eea936..091437c9 100644 --- a/plugins/easy_model_entities/src/tests/versionMatrix.test.js +++ b/plugins/easy_model_entities/src/tests/versionMatrix.test.js @@ -19,7 +19,6 @@ const { getVersions, - getEnabledVersions, getDefaultVersionId, getPackFormats } = require('../model/versionMatrix'); @@ -27,7 +26,7 @@ const { describe('versionMatrix', () => { test('default version is 1.20.1; all four versions are enabled', () => { expect(getDefaultVersionId()).toBe('1.20.1'); - const enabled = getEnabledVersions().map((v) => v.id); + const enabled = getVersions().filter((v) => v.enabled).map((v) => v.id); expect(enabled).toEqual(['1.20.1', '1.21.1', '1.21.11', '26.1.2']); }); diff --git a/plugins/easy_model_entities/src/ui/exportDialog.js b/plugins/easy_model_entities/src/ui/exportDialog.js index 04aa443e..c9fe39ab 100644 --- a/plugins/easy_model_entities/src/ui/exportDialog.js +++ b/plugins/easy_model_entities/src/ui/exportDialog.js @@ -17,13 +17,10 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const {getDefaults, applyTemplate, deepMerge} = require('../model/templates'); const { MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY } = require('../model/presetTypes'); -const {ModelDimensions} = require('../model/ModelDimensions'); -const {VisibleBounds} = require('../model/VisibleBounds'); const {t} = require('../i18n/translations'); const { presetOptions, @@ -37,151 +34,13 @@ const { animationModeOptions, versionOptions } = require('./exportDialogOptions'); - -function settingsToForm(settings) { - const offset = settings.rendering.visibleBoundsOffset || [0, 0, 0]; - - return { - namespace: settings.namespace, - profileId: settings.profileId, - targetVersion: settings.targetVersion, - hostEntityType: settings.host.entityType, - movementType: settings.host.movementType, - bodyType: settings.host.bodyType, - width: settings.dimensions.width, - height: settings.dimensions.height, - eyeHeight: settings.dimensions.eyeHeight, - speed: settings.movement.speed, - stepHeight: settings.movement.stepHeight, - gravity: settings.movement.gravity, - behaviorMode: settings.behavior.mode, - maxHealth: settings.attributes.maxHealth, - movementSpeed: settings.attributes.movementSpeed, - followRange: settings.attributes.followRange, - scale: settings.rendering.scale, - shadowRadius: settings.rendering.shadowRadius, - visibleBoundsWidth: settings.rendering.visibleBoundsWidth ?? 0, - visibleBoundsHeight: settings.rendering.visibleBoundsHeight ?? 0, - visibleBoundsOffsetX: offset[0], - visibleBoundsOffsetY: offset[1], - visibleBoundsOffsetZ: offset[2], - animationMode: settings.animation.mode, - swingSpeed: settings.animation.swingSpeed, - walkSpeedMultiplier: settings.animation.walkSpeedMultiplier, - idleStrength: settings.animation.idleStrength ?? 1, - gait: settings.animation.gait || 'natural' - }; -} - -function formToSettings(form, base) { - return { - schemaVersion: base.schemaVersion, - modelType: base.modelType, - presetType: base.presetType, - namespace: String(form.namespace || '').trim(), - profileId: String(form.profileId || '').trim(), - targetVersion: base.targetVersion, - host: { - entityType: form.hostEntityType, - movementType: form.movementType, - bodyType: form.bodyType - }, - dimensions: { - width: Number(form.width), - height: Number(form.height), - eyeHeight: Number(form.eyeHeight) - }, - movement: { - speed: Number(form.speed), - stepHeight: Number(form.stepHeight), - gravity: !!form.gravity - }, - behavior: { - mode: form.behaviorMode, - lookAtPlayers: base.behavior ? base.behavior.lookAtPlayers : true, - randomStroll: base.behavior ? base.behavior.randomStroll : false - }, - attributes: { - maxHealth: Number(form.maxHealth), - movementSpeed: Number(form.movementSpeed), - followRange: Number(form.followRange) - }, - rendering: { - scale: Number(form.scale), - shadowRadius: Number(form.shadowRadius), - visibleBoundsWidth: Number(form.visibleBoundsWidth), - visibleBoundsHeight: Number(form.visibleBoundsHeight), - visibleBoundsOffset: [ - Number(form.visibleBoundsOffsetX), - Number(form.visibleBoundsOffsetY), - Number(form.visibleBoundsOffsetZ) - ] - }, - animation: { - mode: form.animationMode, - swingSpeed: Number(form.swingSpeed), - walkSpeedMultiplier: Number(form.walkSpeedMultiplier), - idleStrength: Number(form.idleStrength), - gait: form.gait - } - }; -} - -function activeModelType(form) { - return form.exportType === 'model_only' - ? MODEL_TYPE_ENTITY : (form.modelType || MODEL_TYPE_ENTITY); -} - -function activePreset(form) { - return activeModelType(form) === MODEL_TYPE_BLOCK_ENTITY - ? (form.blockPreset || 'static') : (form.preset || 'custom'); -} - -function presetFormValues(presetType, modelType, modelDimensions, - visibleBounds) { - const settings = ModelDimensions.applyModelDimensions( - applyTemplate(presetType, modelType), modelDimensions); - VisibleBounds.applyVisibleBounds(settings, visibleBounds); - const form = settingsToForm(settings); - delete form.namespace; - delete form.profileId; - delete form.targetVersion; - - return form; -} - -function resolveExportSettings(form, base, modelDimensions, visibleBounds) { - const exportType = form.exportType || 'packs'; - const modelType = activeModelType(form); - const preset = activePreset(form); - - let settings; - if (modelType !== MODEL_TYPE_BLOCK_ENTITY && preset === 'custom') { - settings = deepMerge(getDefaults(), base); - } else { - settings = ModelDimensions.applyModelDimensions( - applyTemplate(preset, modelType), modelDimensions); - VisibleBounds.applyVisibleBounds(settings, visibleBounds); - } - - settings.schemaVersion = base.schemaVersion || settings.schemaVersion; - settings.modelType = modelType; - settings.presetType = preset; - settings.namespace = String(form.namespace || '').trim(); - settings.profileId = String(form.profileId || '').trim(); - settings.targetVersion = form.targetVersion; - - if (form.customize) { - settings = formToSettings(form, settings); - } - // Remembered so a customized project reopens with its values (and target). - settings.customize = !!form.customize; - settings.exportType = exportType; - settings.exportTarget = exportType === 'packs' ? 'packs' : 'mod_project'; - settings.modelOnly = exportType === 'model_only'; - - return settings; -} +const { + settingsToForm, + activeModelType, + activePreset, + presetFormValues, + resolveExportSettings +} = require('./exportSettingsMapping'); function advancedField(field, showCustomize) { return Object.assign({condition: showCustomize}, field); @@ -196,11 +55,8 @@ function buildFormConfig(settings, ui) { const blockEntitySelected = (form) => standalone(form) && form.modelType === MODEL_TYPE_BLOCK_ENTITY; const customizeOn = (form) => allowCustomize && !!form.customize; - // Server-profile advanced fields only make sense for a standalone datapack. const showServer = (form) => customizeOn(form) && standalone(form); - // Host/movement/behavior/attributes only apply to standalone entities. const showEntity = (form) => showServer(form) && !blockEntitySelected(form); - // Render-profile advanced fields apply to every export kind. const showRender = customizeOn; const experimental = !!state.experimental; @@ -457,7 +313,6 @@ function openExportDialog(options) { onConfirm(form) { const finalSettings = resolveExportSettings(form, settings, modelDimensions, visibleBounds); - // Records that experimental presets were available for this export. finalSettings.experimental = !!options.experimental; options.onExport(finalSettings, finalSettings.exportTarget); } @@ -468,10 +323,5 @@ function openExportDialog(options) { } module.exports = { - presetOptions, - settingsToForm, - formToSettings, - presetFormValues, - resolveExportSettings, openExportDialog }; diff --git a/plugins/easy_model_entities/src/ui/exportDialogOptions.js b/plugins/easy_model_entities/src/ui/exportDialogOptions.js index 1cb0f200..d4945bf5 100644 --- a/plugins/easy_model_entities/src/ui/exportDialogOptions.js +++ b/plugins/easy_model_entities/src/ui/exportDialogOptions.js @@ -21,12 +21,26 @@ const {getVersions} = require('../model/versionMatrix'); const { SELECTABLE_PRESET_TYPES, BLOCK_ENTITY_PRESET_TYPES, + MOVEMENT_TYPES, + BODY_TYPES, + GAIT_TYPES, + BEHAVIOR_MODES, + ANIMATION_MODES, isStablePreset, MODEL_TYPE_ENTITY, MODEL_TYPE_BLOCK_ENTITY } = require('../model/presetTypes'); const {t} = require('../i18n/translations'); +function optionsFromEnum(values, prefix) { + const options = {}; + values.forEach((id) => { + options[id] = t(`${prefix}.${id}`); + }); + + return options; +} + function presetLabel(id) { return id === 'custom' ? t('eme.preset.custom') : t(`eme.preset.${id}`); } @@ -39,11 +53,10 @@ function entityPresetIds(experimental) { function blockPresetIds(experimental) { return experimental - ? BLOCK_ENTITY_PRESET_TYPES.slice() + ? [...BLOCK_ENTITY_PRESET_TYPES] : BLOCK_ENTITY_PRESET_TYPES.filter(isStablePreset); } -// Experimental (WIP) presets are hidden unless the experimental setting is on. function presetOptions(modelType, experimental, ensure) { const ids = modelType === MODEL_TYPE_BLOCK_ENTITY ? blockPresetIds(experimental) : entityPresetIds(experimental); @@ -83,52 +96,23 @@ function hostEntityTypeOptions() { } function movementTypeOptions() { - return { - ground: t('eme.movement.ground'), - water: t('eme.movement.water'), - amphibious: t('eme.movement.amphibious'), - static: t('eme.movement.static') - }; + return optionsFromEnum(MOVEMENT_TYPES, 'eme.movement'); } function bodyTypeOptions() { - return { - static: t('eme.body.static'), - biped: t('eme.body.biped'), - quadruped: t('eme.body.quadruped'), - aquatic: t('eme.body.aquatic'), - amphibious: t('eme.body.amphibious'), - winged: t('eme.body.winged'), - winged_humanoid: t('eme.body.winged_humanoid'), - arthropod: t('eme.body.arthropod'), - cuboid: t('eme.body.cuboid'), - floating: t('eme.body.floating') - }; + return optionsFromEnum(BODY_TYPES, 'eme.body'); } function gaitOptions() { - return { - natural: t('eme.gait.natural'), - feline: t('eme.gait.feline'), - ungulate: t('eme.gait.ungulate') - }; + return optionsFromEnum(GAIT_TYPES, 'eme.gait'); } function behaviorModeOptions() { - return { - idle_only: t('eme.behavior.idle_only'), - ambient: t('eme.behavior.ambient'), - static: t('eme.behavior.static'), - external_owner: t('eme.behavior.external_owner') - }; + return optionsFromEnum(BEHAVIOR_MODES, 'eme.behavior'); } function animationModeOptions() { - return { - automatic: t('eme.animation.automatic'), - random_idle: t('eme.animation.random_idle'), - none: t('eme.animation.none') - }; + return optionsFromEnum(ANIMATION_MODES, 'eme.animation'); } function versionOptions() { diff --git a/plugins/easy_model_entities/src/ui/exportSettingsMapping.js b/plugins/easy_model_entities/src/ui/exportSettingsMapping.js new file mode 100644 index 00000000..25f3863d --- /dev/null +++ b/plugins/easy_model_entities/src/ui/exportSettingsMapping.js @@ -0,0 +1,179 @@ +/* + * Copyright 2026 Markus Bordihn + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and + * associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +const {getDefaults, applyTemplate, deepMerge} = require('../model/templates'); +const { + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY +} = require('../model/presetTypes'); +const {ModelDimensions} = require('../model/ModelDimensions'); +const {VisibleBounds} = require('../model/VisibleBounds'); + +function settingsToForm(settings) { + const offset = settings.rendering.visibleBoundsOffset || [0, 0, 0]; + + return { + namespace: settings.namespace, + profileId: settings.profileId, + targetVersion: settings.targetVersion, + hostEntityType: settings.host.entityType, + movementType: settings.host.movementType, + bodyType: settings.host.bodyType, + width: settings.dimensions.width, + height: settings.dimensions.height, + eyeHeight: settings.dimensions.eyeHeight, + speed: settings.movement.speed, + stepHeight: settings.movement.stepHeight, + gravity: settings.movement.gravity, + behaviorMode: settings.behavior.mode, + maxHealth: settings.attributes.maxHealth, + movementSpeed: settings.attributes.movementSpeed, + followRange: settings.attributes.followRange, + scale: settings.rendering.scale, + shadowRadius: settings.rendering.shadowRadius, + visibleBoundsWidth: settings.rendering.visibleBoundsWidth ?? 0, + visibleBoundsHeight: settings.rendering.visibleBoundsHeight ?? 0, + visibleBoundsOffsetX: offset[0], + visibleBoundsOffsetY: offset[1], + visibleBoundsOffsetZ: offset[2], + animationMode: settings.animation.mode, + swingSpeed: settings.animation.swingSpeed, + walkSpeedMultiplier: settings.animation.walkSpeedMultiplier, + idleStrength: settings.animation.idleStrength ?? 1, + gait: settings.animation.gait || 'natural' + }; +} + +function formToSettings(form, base) { + return { + schemaVersion: base.schemaVersion, + modelType: base.modelType, + presetType: base.presetType, + namespace: String(form.namespace || '').trim(), + profileId: String(form.profileId || '').trim(), + targetVersion: base.targetVersion, + host: { + entityType: form.hostEntityType, + movementType: form.movementType, + bodyType: form.bodyType + }, + dimensions: { + width: Number(form.width), + height: Number(form.height), + eyeHeight: Number(form.eyeHeight) + }, + movement: { + speed: Number(form.speed), + stepHeight: Number(form.stepHeight), + gravity: !!form.gravity + }, + behavior: { + mode: form.behaviorMode, + lookAtPlayers: base.behavior ? base.behavior.lookAtPlayers : true, + randomStroll: base.behavior ? base.behavior.randomStroll : false + }, + attributes: { + maxHealth: Number(form.maxHealth), + movementSpeed: Number(form.movementSpeed), + followRange: Number(form.followRange) + }, + rendering: { + scale: Number(form.scale), + shadowRadius: Number(form.shadowRadius), + visibleBoundsWidth: Number(form.visibleBoundsWidth), + visibleBoundsHeight: Number(form.visibleBoundsHeight), + visibleBoundsOffset: [ + Number(form.visibleBoundsOffsetX), + Number(form.visibleBoundsOffsetY), + Number(form.visibleBoundsOffsetZ) + ] + }, + animation: { + mode: form.animationMode, + swingSpeed: Number(form.swingSpeed), + walkSpeedMultiplier: Number(form.walkSpeedMultiplier), + idleStrength: Number(form.idleStrength), + gait: form.gait + } + }; +} + +function activeModelType(form) { + return form.exportType === 'model_only' + ? MODEL_TYPE_ENTITY : (form.modelType || MODEL_TYPE_ENTITY); +} + +function activePreset(form) { + return activeModelType(form) === MODEL_TYPE_BLOCK_ENTITY + ? (form.blockPreset || 'static') : (form.preset || 'custom'); +} + +function presetFormValues(presetType, modelType, modelDimensions, + visibleBounds) { + const settings = ModelDimensions.applyModelDimensions( + applyTemplate(presetType, modelType), modelDimensions); + VisibleBounds.applyVisibleBounds(settings, visibleBounds); + const form = settingsToForm(settings); + delete form.namespace; + delete form.profileId; + delete form.targetVersion; + + return form; +} + +function resolveExportSettings(form, base, modelDimensions, visibleBounds) { + const exportType = form.exportType || 'packs'; + const modelType = activeModelType(form); + const preset = activePreset(form); + + let settings; + if (modelType !== MODEL_TYPE_BLOCK_ENTITY && preset === 'custom') { + settings = deepMerge(getDefaults(), base); + } else { + settings = ModelDimensions.applyModelDimensions( + applyTemplate(preset, modelType), modelDimensions); + VisibleBounds.applyVisibleBounds(settings, visibleBounds); + } + + settings.schemaVersion = base.schemaVersion || settings.schemaVersion; + settings.modelType = modelType; + settings.presetType = preset; + settings.namespace = String(form.namespace || '').trim(); + settings.profileId = String(form.profileId || '').trim(); + settings.targetVersion = form.targetVersion; + + if (form.customize) { + settings = formToSettings(form, settings); + } + settings.customize = !!form.customize; + settings.exportType = exportType; + settings.exportTarget = exportType === 'packs' ? 'packs' : 'mod_project'; + settings.modelOnly = exportType === 'model_only'; + + return settings; +} + +module.exports = { + settingsToForm, + formToSettings, + activeModelType, + activePreset, + presetFormValues, + resolveExportSettings +}; From d51cc063349236793cc082b8205a4d245232b331 Mon Sep 17 00:00:00 2001 From: Markus Bordihn Date: Mon, 6 Jul 2026 21:32:37 +0200 Subject: [PATCH 6/8] Update schema version to 0.2.0 and adjust file paths for render profiles. --- .../easy_model_entities.js | 21 +++------ .../src/builders/exporter.js | 12 +---- .../src/builders/renderProfile.js | 3 ++ .../src/builders/serverProfile.js | 4 -- .../src/model/presetTypes.js | 2 +- .../easy_model_entities/src/package-lock.json | 44 +++++++++---------- .../src/tests/contractSync.test.js | 21 ++++----- .../src/tests/exporter.test.js | 35 ++++++++------- ...i-0.1.0.json => easy-model-api-0.2.0.json} | 2 +- .../src/tests/modUpdate.test.js | 2 +- .../src/tests/schemaConsistency.test.js | 31 +++++++------ 11 files changed, 82 insertions(+), 95 deletions(-) rename plugins/easy_model_entities/src/tests/fixtures/{easy-model-api-0.1.0.json => easy-model-api-0.2.0.json} (98%) diff --git a/plugins/easy_model_entities/easy_model_entities.js b/plugins/easy_model_entities/easy_model_entities.js index c447ae18..b853a9ad 100644 --- a/plugins/easy_model_entities/easy_model_entities.js +++ b/plugins/easy_model_entities/easy_model_entities.js @@ -357,20 +357,16 @@ function buildProfiles(settings, textureResolution) { function dataPaths(settings) { const namespace = settings.namespace; const id = settings.profileId; - // Server profiles live in an entity/ or block_entity/ subfolder. The model - // stays at the mod's auto-resolved default location so the render profile - // never has to spell out a model path. const modelType = settings.modelType || 'entity'; return { profile: `data/${namespace}/easy_model_entities/profiles/${modelType}/${id}.json`, - renderProfile: `assets/${namespace}/easy_model_entities/render_profiles/${id}.json`, + renderProfile: + `assets/${namespace}/easy_model_entities/render_profiles/${modelType}/${id}.json`, model: `assets/${namespace}/easy_model_entities/models/${id}.bbmodel` }; } -// One binary entry per custom (packed) texture; external textures are -// referenced by resource location and never written. function textureFiles(settings, textureResolution) { const namespace = settings.namespace; const packed = (textureResolution && textureResolution.packed) || []; @@ -408,8 +404,6 @@ function resourcepackFiles(settings, renderProfile, options) { ]; } -// Inner pack file names are prefixed with the profile id so a user dropping -// several exported packs into one folder never overwrites another model's pack. function packFileNames(settings) { return { datapack: `${settings.profileId}_datapack.zip`, @@ -443,8 +437,6 @@ function buildModProjectFiles(settings, options) { const paths = dataPaths(settings); const files = []; - // Model-only export (mod integration, e.g. the Mimic example): the mod ships - // its own entity classes, so no server profile / data pack is written. const serverProfile = settings.modelOnly ? null : buildServerProfile( settings); stampVersion(serverProfile, renderProfile); @@ -736,6 +728,9 @@ function buildRenderProfile(settings, textureResolution) { profile.body_type = settings.host.bodyType; } + profile.model = + `${settings.namespace}:easy_model_entities/models/${settings.profileId}`; + if (textureResolution && textureResolution.texture) { profile.texture = textureResolution.texture; } @@ -862,10 +857,6 @@ function buildServerProfile(settings) { preset_type: settings.presetType }; - profile.client = { - render_profile: `${settings.namespace}:${settings.profileId}` - }; - if (!blockEntity) { assignIfPresent(profile, 'entity', buildEntity(settings, custom)); } @@ -1953,7 +1944,7 @@ module.exports = {VisibleBounds}; // Mirrors de.markusbordihn.easymodelentities.profile.ModelPresetType. -const SCHEMA_VERSION = '0.1.0'; +const SCHEMA_VERSION = '0.2.0'; const MODEL_TYPE_ENTITY = 'entity'; const MODEL_TYPE_BLOCK_ENTITY = 'block_entity'; diff --git a/plugins/easy_model_entities/src/builders/exporter.js b/plugins/easy_model_entities/src/builders/exporter.js index 78dec82a..1c53ca08 100644 --- a/plugins/easy_model_entities/src/builders/exporter.js +++ b/plugins/easy_model_entities/src/builders/exporter.js @@ -54,20 +54,16 @@ function buildProfiles(settings, textureResolution) { function dataPaths(settings) { const namespace = settings.namespace; const id = settings.profileId; - // Server profiles live in an entity/ or block_entity/ subfolder. The model - // stays at the mod's auto-resolved default location so the render profile - // never has to spell out a model path. const modelType = settings.modelType || 'entity'; return { profile: `data/${namespace}/easy_model_entities/profiles/${modelType}/${id}.json`, - renderProfile: `assets/${namespace}/easy_model_entities/render_profiles/${id}.json`, + renderProfile: + `assets/${namespace}/easy_model_entities/render_profiles/${modelType}/${id}.json`, model: `assets/${namespace}/easy_model_entities/models/${id}.bbmodel` }; } -// One binary entry per custom (packed) texture; external textures are -// referenced by resource location and never written. function textureFiles(settings, textureResolution) { const namespace = settings.namespace; const packed = (textureResolution && textureResolution.packed) || []; @@ -105,8 +101,6 @@ function resourcepackFiles(settings, renderProfile, options) { ]; } -// Inner pack file names are prefixed with the profile id so a user dropping -// several exported packs into one folder never overwrites another model's pack. function packFileNames(settings) { return { datapack: `${settings.profileId}_datapack.zip`, @@ -140,8 +134,6 @@ function buildModProjectFiles(settings, options) { const paths = dataPaths(settings); const files = []; - // Model-only export (mod integration, e.g. the Mimic example): the mod ships - // its own entity classes, so no server profile / data pack is written. const serverProfile = settings.modelOnly ? null : buildServerProfile( settings); stampVersion(serverProfile, renderProfile); diff --git a/plugins/easy_model_entities/src/builders/renderProfile.js b/plugins/easy_model_entities/src/builders/renderProfile.js index 04f4d584..b1c108c5 100644 --- a/plugins/easy_model_entities/src/builders/renderProfile.js +++ b/plugins/easy_model_entities/src/builders/renderProfile.js @@ -86,6 +86,9 @@ function buildRenderProfile(settings, textureResolution) { profile.body_type = settings.host.bodyType; } + profile.model = + `${settings.namespace}:easy_model_entities/models/${settings.profileId}`; + if (textureResolution && textureResolution.texture) { profile.texture = textureResolution.texture; } diff --git a/plugins/easy_model_entities/src/builders/serverProfile.js b/plugins/easy_model_entities/src/builders/serverProfile.js index 6eeea23f..f0c7202b 100644 --- a/plugins/easy_model_entities/src/builders/serverProfile.js +++ b/plugins/easy_model_entities/src/builders/serverProfile.js @@ -102,10 +102,6 @@ function buildServerProfile(settings) { preset_type: settings.presetType }; - profile.client = { - render_profile: `${settings.namespace}:${settings.profileId}` - }; - if (!blockEntity) { assignIfPresent(profile, 'entity', buildEntity(settings, custom)); } diff --git a/plugins/easy_model_entities/src/model/presetTypes.js b/plugins/easy_model_entities/src/model/presetTypes.js index 4f765893..24ab9ae9 100644 --- a/plugins/easy_model_entities/src/model/presetTypes.js +++ b/plugins/easy_model_entities/src/model/presetTypes.js @@ -19,7 +19,7 @@ // Mirrors de.markusbordihn.easymodelentities.profile.ModelPresetType. -const SCHEMA_VERSION = '0.1.0'; +const SCHEMA_VERSION = '0.2.0'; const MODEL_TYPE_ENTITY = 'entity'; const MODEL_TYPE_BLOCK_ENTITY = 'block_entity'; diff --git a/plugins/easy_model_entities/src/package-lock.json b/plugins/easy_model_entities/src/package-lock.json index d52b6f5d..866e987c 100644 --- a/plugins/easy_model_entities/src/package-lock.json +++ b/plugins/easy_model_entities/src/package-lock.json @@ -2078,9 +2078,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.41", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.41.tgz", - "integrity": "sha512-WwS7MHhqGHHlaVsqRZnhvCEMS0owDX+SxRlve7JkuH7My1Ara3ZriTmCQupPfYjxMZ8I/tgxtJYr2t7taHaH4A==", + "version": "2.10.42", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz", + "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2122,9 +2122,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", - "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "version": "4.28.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.5.tgz", + "integrity": "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ==", "dev": true, "funding": [ { @@ -2142,10 +2142,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.38", - "caniuse-lite": "^1.0.30001799", - "electron-to-chromium": "^1.5.376", - "node-releases": "^2.0.48", + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001800", + "electron-to-chromium": "^1.5.387", + "node-releases": "^2.0.50", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -2193,9 +2193,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001800", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", - "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", + "version": "1.0.30001802", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001802.tgz", + "integrity": "sha512-vmv8ub2xwTNmljSKf82mtCk5JH7hC+YgzLj3P5zotvA0tPQ9016tdNNOG8WRca1IxOnhSsivB+J0z5FeE5LOUw==", "dev": true, "funding": [ { @@ -2561,9 +2561,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.385", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.385.tgz", - "integrity": "sha512-78sa/M08MNAYHQfjoWMvOlKQqZ0ElhSm/L5HNUc96VZ3b+KvDVnngFm8sYQy0XrhTRgAhggHr5abA7yTvRdo4Q==", + "version": "1.5.387", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.387.tgz", + "integrity": "sha512-TaxwufTFDufvPEoXdhwVrA3UdFWBeWGkYoJ1K8ldF1xe6gKfth6iRNS5lTQ5JPNOHdGQm8PT1QYKUqFLCiUefQ==", "dev": true, "license": "ISC" }, @@ -2588,9 +2588,9 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.24.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.1.tgz", - "integrity": "sha512-7DdUaTjmNwMcH2gLr1qycesKII3BK4RLy/mdAb7x10Lq7bR4aNKHt1BR1ZALSv0rPM/hF5wYF0PhGop/rJm8vw==", + "version": "5.24.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz", + "integrity": "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==", "dev": true, "license": "MIT", "dependencies": { @@ -5522,9 +5522,9 @@ } }, "node_modules/webpack-sources": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz", - "integrity": "sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", + "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", "dev": true, "license": "MIT", "engines": { diff --git a/plugins/easy_model_entities/src/tests/contractSync.test.js b/plugins/easy_model_entities/src/tests/contractSync.test.js index d933a7c3..240e903c 100644 --- a/plugins/easy_model_entities/src/tests/contractSync.test.js +++ b/plugins/easy_model_entities/src/tests/contractSync.test.js @@ -17,7 +17,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -const contract = require('./fixtures/easy-model-api-0.1.0.json'); +const contract = require('./fixtures/easy-model-api-0.2.0.json'); const {buildRenderProfile} = require('../builders/renderProfile'); const {buildServerProfile} = require('../builders/serverProfile'); const {Validator} = require('../model/Validator'); @@ -89,14 +89,15 @@ describe('mod example style stays minimal', () => { settings.profileId = 'little_explorer'; expect(buildServerProfile(settings)).toEqual({ - schema_version: '0.1.0', + schema_version: '0.2.0', model_type: 'entity', - preset_type: 'humanoid_wandering', - client: {render_profile: 'easy_model_entities_examples:little_explorer'} + preset_type: 'humanoid_wandering' }); expect(buildRenderProfile(settings)).toEqual({ - schema_version: '0.1.0', - preset_type: 'humanoid_wandering' + schema_version: '0.2.0', + preset_type: 'humanoid_wandering', + model: + 'easy_model_entities_examples:easy_model_entities/models/little_explorer' }); }); @@ -106,14 +107,14 @@ describe('mod example style stays minimal', () => { settings.profileId = 'shrine'; expect(buildServerProfile(settings)).toEqual({ - schema_version: '0.1.0', + schema_version: '0.2.0', model_type: 'block_entity', - preset_type: 'animated_randomly', - client: {render_profile: 'easy_model_entities_examples:shrine'} + preset_type: 'animated_randomly' }); expect(buildRenderProfile(settings)).toEqual({ - schema_version: '0.1.0', + schema_version: '0.2.0', preset_type: 'static', + model: 'easy_model_entities_examples:easy_model_entities/models/shrine', rendering: {shadow_radius: 0.5}, animation: {mode: 'random_idle'} }); diff --git a/plugins/easy_model_entities/src/tests/exporter.test.js b/plugins/easy_model_entities/src/tests/exporter.test.js index 075f9818..34e9e9b9 100644 --- a/plugins/easy_model_entities/src/tests/exporter.test.js +++ b/plugins/easy_model_entities/src/tests/exporter.test.js @@ -40,7 +40,7 @@ describe('buildPackBundle', () => { ]); expect(bundle.resourcepack.map((f) => f.path).sort()).toEqual([ 'assets/example/easy_model_entities/models/lizard.bbmodel', - 'assets/example/easy_model_entities/render_profiles/lizard.json', + 'assets/example/easy_model_entities/render_profiles/entity/lizard.json', 'assets/example/textures/entity/lizard.png', 'pack.mcmeta' ].sort()); @@ -61,18 +61,19 @@ describe('buildPackBundle', () => { }); test( - 'profiles carry model_type, preset_type, render link, no obsolete fields', + 'profiles carry model_type, preset_type, aligned model ref, no obsolete fields', () => { const server = JSON.parse(fileByPath(bundle.datapack, ENTITY_PROFILE_PATH).content); const render = JSON.parse(fileByPath(bundle.resourcepack, - 'assets/example/easy_model_entities/render_profiles/lizard.json') + 'assets/example/easy_model_entities/render_profiles/entity/lizard.json') .content); expect(server.model_type).toBe('entity'); expect(server.preset_type).toBe('quadruped_wandering'); - expect(server.schema_version).toBe('0.1.0'); - expect(server.client.render_profile).toBe('example:lizard'); + expect(server.schema_version).toBe('0.2.0'); + expect(server.client).toBeUndefined(); expect(render.preset_type).toBe('quadruped_wandering'); + expect(render.model).toBe('example:easy_model_entities/models/lizard'); expect(server.host).toBeUndefined(); expect(server.pack_pair).toBeUndefined(); expect(server.id).toBeUndefined(); @@ -104,7 +105,7 @@ describe('buildModProjectFiles', () => { fixtureExportOptions()); expect(files.map((f) => f.path)).toEqual([ ENTITY_PROFILE_PATH, - 'assets/example/easy_model_entities/render_profiles/lizard.json', + 'assets/example/easy_model_entities/render_profiles/entity/lizard.json', 'assets/example/easy_model_entities/models/lizard.bbmodel', 'assets/example/textures/entity/lizard.png' ]); @@ -137,7 +138,7 @@ describe('model-only export (mod integration)', () => { fixtureExportOptions(settings)); expect(serverProfile).toBeNull(); expect(files.map((f) => f.path)).toEqual([ - 'assets/example/easy_model_entities/render_profiles/chestling.json', + 'assets/example/easy_model_entities/render_profiles/entity/chestling.json', 'assets/example/easy_model_entities/models/chestling.bbmodel', 'assets/example/textures/entity/chestling.png' ]); @@ -213,7 +214,7 @@ describe('mixed vanilla + custom textures', () => { ['assets/example/textures/entity/lizard_1.png']); const render = JSON.parse(fileByPath(files, - 'assets/example/easy_model_entities/render_profiles/lizard.json') + 'assets/example/easy_model_entities/render_profiles/entity/lizard.json') .content); expect(render.texture).toBe('minecraft:textures/entity/chest/normal.png'); expect(render.textures).toEqual( @@ -229,11 +230,15 @@ describe('block entity export uses the block_entity subfolder', () => { return settings; } - test('server profile lands under profiles/block_entity', () => { - const settings = blockSettings(); - const {files} = buildModProjectFiles(settings, - fixtureExportOptions(settings)); - expect(files.map((f) => f.path)).toContain( - 'data/example/easy_model_entities/profiles/block_entity/shrine.json'); - }); + test('server and render profiles land under the block_entity subfolder', + () => { + const settings = blockSettings(); + const {files} = buildModProjectFiles(settings, + fixtureExportOptions(settings)); + const paths = files.map((f) => f.path); + expect(paths).toContain( + 'data/example/easy_model_entities/profiles/block_entity/shrine.json'); + expect(paths).toContain( + 'assets/example/easy_model_entities/render_profiles/block_entity/shrine.json'); + }); }); diff --git a/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.1.0.json b/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.2.0.json similarity index 98% rename from plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.1.0.json rename to plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.2.0.json index 2fb9610f..4493bba1 100644 --- a/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.1.0.json +++ b/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.2.0.json @@ -1,5 +1,5 @@ { - "schema_version": "0.1.0", + "schema_version": "0.2.0", "budgets": { "max_model_file_size_bytes": 2097152, "soft_model_file_size_bytes": 1048576, diff --git a/plugins/easy_model_entities/src/tests/modUpdate.test.js b/plugins/easy_model_entities/src/tests/modUpdate.test.js index e71dd2aa..fdb5a853 100644 --- a/plugins/easy_model_entities/src/tests/modUpdate.test.js +++ b/plugins/easy_model_entities/src/tests/modUpdate.test.js @@ -72,7 +72,7 @@ describe('pickModelSettings keeps reopen-critical settings', () => { expect(picked.modelOnly).toBeUndefined(); expect(picked.experimental).toBeUndefined(); // Model-defining data is retained. - expect(picked.schemaVersion).toBe('0.1.0'); + expect(picked.schemaVersion).toBe('0.2.0'); expect(picked.presetType).toBe('humanoid_wandering'); expect(picked.host).toEqual(full.host); expect(picked.rendering).toEqual(full.rendering); diff --git a/plugins/easy_model_entities/src/tests/schemaConsistency.test.js b/plugins/easy_model_entities/src/tests/schemaConsistency.test.js index 886dd7f8..543d673e 100644 --- a/plugins/easy_model_entities/src/tests/schemaConsistency.test.js +++ b/plugins/easy_model_entities/src/tests/schemaConsistency.test.js @@ -32,21 +32,22 @@ describe('preset defaults mirror the mod (minimal output)', () => { describe.each(SELECTABLE_PRESET_TYPES)('%s', (preset) => { const settings = withId(applyTemplate(preset)); - test('server profile reduces to identity + render link', () => { + test('server profile reduces to identity only', () => { expect(buildServerProfile(settings)).toEqual({ - schema_version: '0.1.0', + schema_version: '0.2.0', model_type: 'entity', - preset_type: preset, - client: {render_profile: 'example:lizard'} - }); - }); - - test('render profile reduces to schema_version + preset_type', () => { - expect(buildRenderProfile(settings)).toEqual({ - schema_version: '0.1.0', preset_type: preset }); }); + + test('render profile reduces to schema_version, preset_type and model', + () => { + expect(buildRenderProfile(settings)).toEqual({ + schema_version: '0.2.0', + preset_type: preset, + model: 'example:easy_model_entities/models/lizard' + }); + }); }); }); @@ -79,14 +80,13 @@ describe('block entity profiles', () => { return settings; } - test('server profile carries model_type, render link and no entity behavior', + test('server profile carries model_type and no entity behavior', () => { const profile = buildServerProfile(blockSettings('animated_randomly')); expect(profile).toEqual({ - schema_version: '0.1.0', + schema_version: '0.2.0', model_type: 'block_entity', - preset_type: 'animated_randomly', - client: {render_profile: 'example:shrine'} + preset_type: 'animated_randomly' }); expect(profile.entity).toBeUndefined(); expect(profile.movement).toBeUndefined(); @@ -118,10 +118,9 @@ describe('customized settings emit only the deviating values', () => { test('server profile contains the stone turtle overrides only', () => { expect(buildServerProfile(settings)).toEqual({ - schema_version: '0.1.0', + schema_version: '0.2.0', model_type: 'entity', preset_type: 'quadruped_wandering', - client: {render_profile: 'example:lizard'}, dimensions: {width: 1.2, height: 0.85}, movement: {speed: 0.06, step_height: 0.4}, attributes: {max_health: 12, follow_range: 12} From 727b55b29d977cb7e3cfb1476e8e40fda08ed574 Mon Sep 17 00:00:00 2001 From: Markus Bordihn Date: Mon, 13 Jul 2026 20:41:32 +0200 Subject: [PATCH 7/8] Updated list of supported animation clips. --- plugins/easy_model_entities/easy_model_entities.js | 2 +- plugins/easy_model_entities/src/model/presetTypes.js | 2 +- .../src/tests/fixtures/easy-model-api-0.2.0.json | 5 ++++- plugins/easy_model_entities/src/tests/validation.test.js | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/easy_model_entities/easy_model_entities.js b/plugins/easy_model_entities/easy_model_entities.js index b853a9ad..8c7b7b8e 100644 --- a/plugins/easy_model_entities/easy_model_entities.js +++ b/plugins/easy_model_entities/easy_model_entities.js @@ -1966,7 +1966,7 @@ const BODY_TYPES = [ const MOVEMENT_TYPES = ['ground', 'water', 'amphibious', 'static']; const BEHAVIOR_MODES = ['idle_only', 'ambient', 'static', 'external_owner']; const ANIMATION_MODES = ['automatic', 'random_idle', 'none']; -const ANIMATION_CLIPS = ['idle', 'walk', 'swim', 'fly']; +const ANIMATION_CLIPS = ['idle', 'walk', 'swim', 'fly', 'hurt', 'death', 'attack']; const GAIT_TYPES = ['natural', 'feline', 'ungulate']; const GROUND_ENTITY = 'easy_model_entities:ground_entity'; diff --git a/plugins/easy_model_entities/src/model/presetTypes.js b/plugins/easy_model_entities/src/model/presetTypes.js index 24ab9ae9..c37426f8 100644 --- a/plugins/easy_model_entities/src/model/presetTypes.js +++ b/plugins/easy_model_entities/src/model/presetTypes.js @@ -41,7 +41,7 @@ const BODY_TYPES = [ const MOVEMENT_TYPES = ['ground', 'water', 'amphibious', 'static']; const BEHAVIOR_MODES = ['idle_only', 'ambient', 'static', 'external_owner']; const ANIMATION_MODES = ['automatic', 'random_idle', 'none']; -const ANIMATION_CLIPS = ['idle', 'walk', 'swim', 'fly']; +const ANIMATION_CLIPS = ['idle', 'walk', 'swim', 'fly', 'hurt', 'death', 'attack']; const GAIT_TYPES = ['natural', 'feline', 'ungulate']; const GROUND_ENTITY = 'easy_model_entities:ground_entity'; diff --git a/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.2.0.json b/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.2.0.json index 4493bba1..cbe3b917 100644 --- a/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.2.0.json +++ b/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.2.0.json @@ -79,7 +79,10 @@ "idle", "walk", "swim", - "fly" + "fly", + "hurt", + "death", + "attack" ], "gaits": [ "natural", diff --git a/plugins/easy_model_entities/src/tests/validation.test.js b/plugins/easy_model_entities/src/tests/validation.test.js index b24b4d7e..c484caf4 100644 --- a/plugins/easy_model_entities/src/tests/validation.test.js +++ b/plugins/easy_model_entities/src/tests/validation.test.js @@ -165,7 +165,7 @@ describe('validateSettings', () => { hasTexture: true, animationCount: 3, animations: [ - {name: 'attack', channels: ['rotation'], hasExpression: false}, + {name: 'dance', channels: ['rotation'], hasExpression: false}, {name: 'walk', channels: ['rotation'], hasExpression: true}, {name: 'idle', channels: ['rotation', 'scale'], hasExpression: false} ], From 044c01d4dd66ef71779b882ea48ddb75c454d54a Mon Sep 17 00:00:00 2001 From: Markus Bordihn Date: Thu, 16 Jul 2026 01:50:21 +0200 Subject: [PATCH 8/8] Refactored code by removing commented-out sections and improving formatting for better readability. --- plugins/easy_model_entities/about.md | 3 +- .../easy_model_entities.js | 27 +---- .../src/builders/exporter.js | 6 -- .../src/builders/profileDiff.js | 3 - .../src/i18n/translations.js | 5 - .../src/model/presetTypes.js | 5 +- .../easy_model_entities/src/package-lock.json | 98 +++++++++---------- plugins/easy_model_entities/src/package.json | 2 +- .../src/ui/texturePanel.js | 1 - .../src/utils/ResourceLocation.js | 1 - .../src/utils/TextureResolver.js | 4 - plugins/easy_model_entities/src/utils/hash.js | 2 - 12 files changed, 56 insertions(+), 101 deletions(-) diff --git a/plugins/easy_model_entities/about.md b/plugins/easy_model_entities/about.md index e73e430f..3f75fd31 100644 --- a/plugins/easy_model_entities/about.md +++ b/plugins/easy_model_entities/about.md @@ -15,7 +15,8 @@ installed** in Minecraft for them to work. Without the mod the generated files d 1. Open or create your model in Blockbench using the **Easy Model Entity** format (File > New > Easy Model Entity), this is the recommended format for this plugin. - Alternatively, the built-in **Modded Entity** format (File > New > Java/Modded Entity) is also supported. Block and item formats are not supported. + Alternatively, the built-in **Modded Entity** format (File > New > Java/Modded Entity) is also + supported. Block and item formats are not supported. 2. Run **File > Export > Export Easy Model Entities**. 3. Choose the **Export Type**: - **Standalone: Data Pack + Resource Pack (ZIP)** — a single archive with a `datapack.zip` and a diff --git a/plugins/easy_model_entities/easy_model_entities.js b/plugins/easy_model_entities/easy_model_entities.js index 8c7b7b8e..5f38b4a8 100644 --- a/plugins/easy_model_entities/easy_model_entities.js +++ b/plugins/easy_model_entities/easy_model_entities.js @@ -330,12 +330,6 @@ function toJson(value) { return JSON.stringify(value, null, 2) + '\n'; } -// The version stamp is a content hash of the server profile, i.e. of exactly -// the server-relevant settings (type, preset, dimensions, behavior, …) and not -// of the model geometry or textures. Both profiles receive the same stamp so -// the mod's server/client parity check always matches; it only changes when a -// server-relevant setting changes. Model-only exports have no server profile -// and stay version-less (the mod's own runtime contract owns the version). function stampVersion(serverProfile, renderProfile) { if (!serverProfile) { return; @@ -549,9 +543,6 @@ module.exports = {buildDataPackMcmeta, buildResourcePackMcmeta}; * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Helpers to emit minimal (diff-based) profiles: only values that deviate from -// the defaults the mod derives from preset_type are written. - function valuesDiffer(a, b) { if (Array.isArray(a) || Array.isArray(b)) { return JSON.stringify(a) !== JSON.stringify(b); @@ -1058,10 +1049,6 @@ module.exports = {FORMAT_ID, registerEmeFormat, unregisterEmeFormat}; * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Descriptive, human-readable labels so users see e.g. "Quadruped (4 legs, -// ground, standing)" instead of the cryptic "quadruped_still". Registered with -// Blockbench's translation system; falls back to English outside Blockbench. - const EN = { 'eme.dialog.title': 'Easy Model Entities Export', 'eme.field.preset': 'Preset', @@ -1272,7 +1259,6 @@ function registerTranslations() { } } -// Resolves a key via Blockbench's tl() when available, otherwise English. function t(key) { if (typeof tl === 'function') { const translated = tl(key); @@ -1942,8 +1928,6 @@ module.exports = {VisibleBounds}; * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Mirrors de.markusbordihn.easymodelentities.profile.ModelPresetType. - const SCHEMA_VERSION = '0.2.0'; const MODEL_TYPE_ENTITY = 'entity'; @@ -1966,7 +1950,8 @@ const BODY_TYPES = [ const MOVEMENT_TYPES = ['ground', 'water', 'amphibious', 'static']; const BEHAVIOR_MODES = ['idle_only', 'ambient', 'static', 'external_owner']; const ANIMATION_MODES = ['automatic', 'random_idle', 'none']; -const ANIMATION_CLIPS = ['idle', 'walk', 'swim', 'fly', 'hurt', 'death', 'attack']; +const ANIMATION_CLIPS = ['idle', 'walk', 'swim', 'fly', 'hurt', 'death', + 'attack']; const GAIT_TYPES = ['natural', 'feline', 'ungulate']; const GROUND_ENTITY = 'easy_model_entities:ground_entity'; @@ -3260,7 +3245,6 @@ function patchTexturePanel(formatId) { return `${base} - ${label}`; } } catch (error) { - // Display nicety only; never break the panel render. } return base; @@ -3340,7 +3324,6 @@ class ResourceLocation { return `${namespace}:${path}`; } - // e.g. "My Model.bbmodel" -> "my_model" static sanitizeProfileId(name) { const base = String(name || '') .replace(/\.[^.]+$/, '') @@ -3379,10 +3362,6 @@ module.exports = {ResourceLocation}; * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Resolves Blockbench textures to mod resource locations. A texture that lives -// under an existing assets//textures tree (vanilla or another mod) is only -// referenced; everything else is a custom texture packed as a PNG file. - const ASSETS_PATTERN = /assets\/([a-z0-9_.-]+)\/textures\/(.+?)(?:\.png)?$/i; function stripPng(value) { @@ -3501,8 +3480,6 @@ module.exports = { * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Deterministic, dependency-free 32-bit FNV-1a hash rendered as 8 hex chars. -// Used to derive a stable content version stamp without requiring node crypto. function hashString(value) { let hash = 0x811c9dc5; const text = String(value); diff --git a/plugins/easy_model_entities/src/builders/exporter.js b/plugins/easy_model_entities/src/builders/exporter.js index 1c53ca08..a6172f14 100644 --- a/plugins/easy_model_entities/src/builders/exporter.js +++ b/plugins/easy_model_entities/src/builders/exporter.js @@ -27,12 +27,6 @@ function toJson(value) { return JSON.stringify(value, null, 2) + '\n'; } -// The version stamp is a content hash of the server profile, i.e. of exactly -// the server-relevant settings (type, preset, dimensions, behavior, …) and not -// of the model geometry or textures. Both profiles receive the same stamp so -// the mod's server/client parity check always matches; it only changes when a -// server-relevant setting changes. Model-only exports have no server profile -// and stay version-less (the mod's own runtime contract owns the version). function stampVersion(serverProfile, renderProfile) { if (!serverProfile) { return; diff --git a/plugins/easy_model_entities/src/builders/profileDiff.js b/plugins/easy_model_entities/src/builders/profileDiff.js index 47c9b4b4..ce635917 100644 --- a/plugins/easy_model_entities/src/builders/profileDiff.js +++ b/plugins/easy_model_entities/src/builders/profileDiff.js @@ -17,9 +17,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Helpers to emit minimal (diff-based) profiles: only values that deviate from -// the defaults the mod derives from preset_type are written. - function valuesDiffer(a, b) { if (Array.isArray(a) || Array.isArray(b)) { return JSON.stringify(a) !== JSON.stringify(b); diff --git a/plugins/easy_model_entities/src/i18n/translations.js b/plugins/easy_model_entities/src/i18n/translations.js index d86bcdd6..33a7d671 100644 --- a/plugins/easy_model_entities/src/i18n/translations.js +++ b/plugins/easy_model_entities/src/i18n/translations.js @@ -17,10 +17,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Descriptive, human-readable labels so users see e.g. "Quadruped (4 legs, -// ground, standing)" instead of the cryptic "quadruped_still". Registered with -// Blockbench's translation system; falls back to English outside Blockbench. - const EN = { 'eme.dialog.title': 'Easy Model Entities Export', 'eme.field.preset': 'Preset', @@ -231,7 +227,6 @@ function registerTranslations() { } } -// Resolves a key via Blockbench's tl() when available, otherwise English. function t(key) { if (typeof tl === 'function') { const translated = tl(key); diff --git a/plugins/easy_model_entities/src/model/presetTypes.js b/plugins/easy_model_entities/src/model/presetTypes.js index c37426f8..44175d4f 100644 --- a/plugins/easy_model_entities/src/model/presetTypes.js +++ b/plugins/easy_model_entities/src/model/presetTypes.js @@ -17,8 +17,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Mirrors de.markusbordihn.easymodelentities.profile.ModelPresetType. - const SCHEMA_VERSION = '0.2.0'; const MODEL_TYPE_ENTITY = 'entity'; @@ -41,7 +39,8 @@ const BODY_TYPES = [ const MOVEMENT_TYPES = ['ground', 'water', 'amphibious', 'static']; const BEHAVIOR_MODES = ['idle_only', 'ambient', 'static', 'external_owner']; const ANIMATION_MODES = ['automatic', 'random_idle', 'none']; -const ANIMATION_CLIPS = ['idle', 'walk', 'swim', 'fly', 'hurt', 'death', 'attack']; +const ANIMATION_CLIPS = ['idle', 'walk', 'swim', 'fly', 'hurt', 'death', + 'attack']; const GAIT_TYPES = ['natural', 'feline', 'ungulate']; const GROUND_ENTITY = 'easy_model_entities:ground_entity'; diff --git a/plugins/easy_model_entities/src/package-lock.json b/plugins/easy_model_entities/src/package-lock.json index 866e987c..9ffd6cb1 100644 --- a/plugins/easy_model_entities/src/package-lock.json +++ b/plugins/easy_model_entities/src/package-lock.json @@ -8,7 +8,7 @@ "name": "easy_model_entities", "version": "1.0.0", "devDependencies": { - "blockbench-types": "5.1.0", + "blockbench-types": "^5.1.0", "copy-webpack-plugin": "14.0.0", "jest": "30.4.2", "webpack": "5.107.2", @@ -1097,9 +1097,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.34.49", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", - "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", "dev": true, "license": "MIT" }, @@ -1241,9 +1241,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.13.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", - "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1310,9 +1310,9 @@ "license": "MIT" }, "node_modules/@ungap/structured-clone": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.2.tgz", - "integrity": "sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", "dev": true, "license": "ISC" }, @@ -2078,9 +2078,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.42", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz", - "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==", + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2112,9 +2112,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", - "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -2122,9 +2122,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.5.tgz", - "integrity": "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ==", + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", "dev": true, "funding": [ { @@ -2143,9 +2143,9 @@ "license": "MIT", "dependencies": { "baseline-browser-mapping": "^2.10.42", - "caniuse-lite": "^1.0.30001800", - "electron-to-chromium": "^1.5.387", - "node-releases": "^2.0.50", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -2193,9 +2193,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001802", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001802.tgz", - "integrity": "sha512-vmv8ub2xwTNmljSKf82mtCk5JH7hC+YgzLj3P5zotvA0tPQ9016tdNNOG8WRca1IxOnhSsivB+J0z5FeE5LOUw==", + "version": "1.0.30001805", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", "dev": true, "funding": [ { @@ -2525,9 +2525,9 @@ } }, "node_modules/dompurify": { - "version": "3.4.11", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.11.tgz", - "integrity": "sha512-zhlUV12GsaRzMsf9q5M254YhA4+VuF0fG+QFqu6aYpoGlKtz+w8//jBcGVYBgQkR5GHjUomejY84AV+/uPbWdw==", + "version": "3.4.12", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", + "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", "dev": true, "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { @@ -2561,9 +2561,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.387", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.387.tgz", - "integrity": "sha512-TaxwufTFDufvPEoXdhwVrA3UdFWBeWGkYoJ1K8ldF1xe6gKfth6iRNS5lTQ5JPNOHdGQm8PT1QYKUqFLCiUefQ==", + "version": "1.5.392", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz", + "integrity": "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==", "dev": true, "license": "ISC" }, @@ -2648,9 +2648,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", - "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", "dev": true, "license": "MIT" }, @@ -4122,9 +4122,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "dev": true, "funding": [ { @@ -4178,9 +4178,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", - "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "dev": true, "license": "MIT", "engines": { @@ -4422,9 +4422,9 @@ } }, "node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", "dev": true, "funding": [ { @@ -5007,9 +5007,9 @@ } }, "node_modules/terser": { - "version": "5.48.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", - "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", + "version": "5.49.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", + "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5144,9 +5144,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/plugins/easy_model_entities/src/package.json b/plugins/easy_model_entities/src/package.json index 2ff86bc5..0c1093e2 100644 --- a/plugins/easy_model_entities/src/package.json +++ b/plugins/easy_model_entities/src/package.json @@ -10,7 +10,7 @@ "test": "jest" }, "devDependencies": { - "blockbench-types": "5.1.0", + "blockbench-types": "^5.1.0", "copy-webpack-plugin": "14.0.0", "jest": "30.4.2", "webpack": "5.107.2", diff --git a/plugins/easy_model_entities/src/ui/texturePanel.js b/plugins/easy_model_entities/src/ui/texturePanel.js index 04c7235b..1d3a90fc 100644 --- a/plugins/easy_model_entities/src/ui/texturePanel.js +++ b/plugins/easy_model_entities/src/ui/texturePanel.js @@ -54,7 +54,6 @@ function patchTexturePanel(formatId) { return `${base} - ${label}`; } } catch (error) { - // Display nicety only; never break the panel render. } return base; diff --git a/plugins/easy_model_entities/src/utils/ResourceLocation.js b/plugins/easy_model_entities/src/utils/ResourceLocation.js index 20a92088..db01f06e 100644 --- a/plugins/easy_model_entities/src/utils/ResourceLocation.js +++ b/plugins/easy_model_entities/src/utils/ResourceLocation.js @@ -55,7 +55,6 @@ class ResourceLocation { return `${namespace}:${path}`; } - // e.g. "My Model.bbmodel" -> "my_model" static sanitizeProfileId(name) { const base = String(name || '') .replace(/\.[^.]+$/, '') diff --git a/plugins/easy_model_entities/src/utils/TextureResolver.js b/plugins/easy_model_entities/src/utils/TextureResolver.js index f8231309..93e37ef5 100644 --- a/plugins/easy_model_entities/src/utils/TextureResolver.js +++ b/plugins/easy_model_entities/src/utils/TextureResolver.js @@ -17,10 +17,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Resolves Blockbench textures to mod resource locations. A texture that lives -// under an existing assets//textures tree (vanilla or another mod) is only -// referenced; everything else is a custom texture packed as a PNG file. - const ASSETS_PATTERN = /assets\/([a-z0-9_.-]+)\/textures\/(.+?)(?:\.png)?$/i; function stripPng(value) { diff --git a/plugins/easy_model_entities/src/utils/hash.js b/plugins/easy_model_entities/src/utils/hash.js index d41a9945..a3e4d48d 100644 --- a/plugins/easy_model_entities/src/utils/hash.js +++ b/plugins/easy_model_entities/src/utils/hash.js @@ -17,8 +17,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -// Deterministic, dependency-free 32-bit FNV-1a hash rendered as 8 hex chars. -// Used to derive a stable content version stamp without requiring node crypto. function hashString(value) { let hash = 0x811c9dc5; const text = String(value);