diff --git a/plugins.json b/plugins.json index c9593818..c3822b22 100644 --- a/plugins.json +++ b/plugins.json @@ -1534,5 +1534,23 @@ "website": "https://svdex.moe", "min_version": "4.8.0", "has_changelog": true + }, + "easy_model_entities": { + "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.", + "tags": ["Minecraft: Java Edition", "Format", "Exporter", "Entity"], + "version": "1.0.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" } } 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..3f75fd31 --- /dev/null +++ b/plugins/easy_model_entities/about.md @@ -0,0 +1,46 @@ +# 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 +> [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 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 + `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. + +## 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..1053d549 --- /dev/null +++ b/plugins/easy_model_entities/changelog.json @@ -0,0 +1,24 @@ +{ + "1.0.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..5f38b4a8 --- /dev/null +++ b/plugins/easy_model_entities/easy_model_entities.js @@ -0,0 +1,3904 @@ +/******/ (() => { // 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. + */ + +const {Validator} = __webpack_require__(229); + +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; + } + + static #firstTexture() { + if (typeof Texture === 'undefined') { + return 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 collectTextures() { + if (typeof Texture === 'undefined' || !Texture.all) { + return []; + } + + 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 #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 : []; + const animations = + typeof Animation !== 'undefined' && Animation.all ? Animation.all : []; + const texture = BlockbenchAdapter.#firstTexture(); + + 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, + animationCount: animations.length, + animations: animations.map( + (animation) => BlockbenchAdapter.#summarizeAnimation(animation)), + 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 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 ''; + } + + 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 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'}); + } + + 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( + { + 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 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 = + 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); +const {hashString} = __webpack_require__(803); + +function toJson(value) { + return JSON.stringify(value, null, 2) + '\n'; +} + +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; + const modelType = settings.modelType || 'entity'; + + return { + profile: `data/${namespace}/easy_model_entities/profiles/${modelType}/${id}.json`, + renderProfile: + `assets/${namespace}/easy_model_entities/render_profiles/${modelType}/${id}.json`, + model: `assets/${namespace}/easy_model_entities/models/${id}.bbmodel` + }; +} + +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) + ]; +} + +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 = []; + const serverProfile = settings.modelOnly ? null : buildServerProfile( + settings); + stampVersion(serverProfile, renderProfile); + if (serverProfile) { + 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}, + ...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 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 buildPack(formats[kind], description); +} + +function buildDataPackMcmeta(settings) { + return buildMcmeta(settings, 'data', + 'Easy Model Entities server profiles'); +} + +function buildResourcePackMcmeta(settings) { + return buildMcmeta(settings, 'resource', + '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. + */ + +function valuesDiffer(a, b) { + if (Array.isArray(a) || Array.isArray(b)) { + return JSON.stringify(a) !== JSON.stringify(b); + } + + return a !== b; +} + +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; +} + +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, + defaultRenderingSettings, + defaultAnimationSettings, + isCustom, + MODEL_TYPE_BLOCK_ENTITY +} = __webpack_require__(151); +const {diffFlat, assignIfPresent} = __webpack_require__(640); + +function renderPresetType(settings) { + return settings.modelType === MODEL_TYPE_BLOCK_ENTITY + ? '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) { + 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 defaults = defaultAnimationSettings( + animationMode(renderPresetType(settings))); + const animation = diffFlat(toAnimationJson(settings.animation), + toAnimationJson(defaults)); + + if (settings.animation.mode === 'none') { + delete animation.swing_speed; + delete animation.walk_speed_multiplier; + delete animation.idle_strength; + delete animation.gait; + } + + return animation; +} + +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 + }; + + if (custom || settings.host.bodyType !== bodyType(renderPreset)) { + profile.body_type = settings.host.bodyType; + } + + profile.model = + `${settings.namespace}:easy_model_entities/models/${settings.profileId}`; + + 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, + wandersByMovement, + isCustom, + DEFAULT_MAX_HEALTH, + DEFAULT_FOLLOW_RANGE, + MODEL_TYPE_BLOCK_ENTITY, + MODEL_TYPE_ENTITY +} = __webpack_require__(151); +const {diffFlat, assignIfPresent} = __webpack_require__(640); + +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: wandersByMovement(movementType) && 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 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 (!blockEntity) { + assignIfPresent(profile, 'entity', buildEntity(settings, custom)); + } + + 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; +} + +module.exports = {buildServerProfile}; + + +/***/ }, + +/***/ 670 +(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 {BlockbenchAdapter} = __webpack_require__(833); +const {pickModelSettings} = __webpack_require__(668); +const {FORMAT_ID} = __webpack_require__(995); + +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] = pickModelSettings(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}; + + +/***/ }, + +/***/ 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, + texture_folder: true, + 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); + } + } + }); +} + +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. + */ + +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.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', + '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.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)', + '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.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', + '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.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)', + '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.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', + '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.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)', + '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.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', + '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.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)', + '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); + } +} + +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 + }; + + static #EYE_HEIGHT_FACTOR = {biped: 0.9, quadruped: 0.8, static: 0.85}; + + static #round(value) { + return Math.round(value * 1000) / 1000; + } + + 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}; + } + + 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; + + if (width <= 0 || height <= 0) { + return {...ModelDimensions.FALLBACK}; + } + + 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) + }; + } + + static applyModelDimensions(settings, modelDimensions) { + if (!modelDimensions) { + return settings; + } + + 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. + */ + +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; + } + + 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), + z: Math.max(0, bounds.maxZ - bounds.minZ) + }; + } + + 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) { + 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'}; + } + } + + 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, + ANIMATION_CLIPS, + MODEL_TYPE_BLOCK_ENTITY +} = __webpack_require__(151); + +const SUPPORTED_ANIMATION_CHANNELS = new Set(['rotation', 'position']); + +class Validator { + static BUDGETS = { + maxTextureSize: 2048, + softTextureSize: 128, + maxModelFileSizeBytes: 2 * 1024 * 1024, + softModelFileSizeBytes: 1024 * 1024, + maxCubeCount: 512, + softCubeCount: 384, + maxBoneCount: 128, + softBoneCount: 96, + maxHierarchyDepth: 32, + softHierarchyDepth: 24, + maxAnimationCount: 16 + }; + + 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 #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 = []; + 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; + 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.textureWidth) + && Validator.#isFiniteNumber(ctx.textureHeight)) { + 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` + }); + 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) { + errors.push({ + code: 'HIGH_ANIMATION_COUNT', + 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({ + code: 'MISSING_BODY_PART', + message: `Missing recommended body part: ${part}` + }); + }); + + 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}; + + +/***/ }, + +/***/ 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 +(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 SCHEMA_VERSION = '0.2.0'; + +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 ANIMATION_CLIPS = ['idle', 'walk', 'swim', 'fly', 'hurt', 'death', + 'attack']; +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}; + +const DEFAULT_MAX_HEALTH = 10; +const DEFAULT_FOLLOW_RANGE = 16; + +const PRESET_TYPES = [ + 'custom', + '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' +]; + +const BLOCK_ENTITY_PRESET_TYPES = [ + 'static', + 'ticking', + 'animated', + 'animated_randomly' +]; + +const STABLE_PRESET_TYPES = new Set([ + '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', + // Block entity stable presets. + 'animated', + 'animated_randomly' +]); + +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 'amphibious_still': + case 'amphibious_wandering': + return 'amphibious'; + 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 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; +} + +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 '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}; + 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}; + } +} + +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': + 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 behaviorModeFor(presetType, move) { + if (isMoving(presetType)) { + return 'ambient'; + } + + if (isStill(presetType)) { + return 'idle_only'; + } + + if (presetType === 'custom') { + return move === 'ground' || move === 'amphibious' ? 'idle_only' : 'static'; + } + + return 'static'; +} + +function behaviorMode(presetType) { + return behaviorModeFor(presetType, movementType(presetType)); +} + +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) { + return { + speed: defaultSpeed(move), + stepHeight: move === 'ground' || move === 'amphibious' ? 0.6 : 0, + gravity: presetType === 'static' || move !== 'static' + }; +} + +function animationMode(presetType) { + return presetType === 'custom' || presetType === 'static' + || presetType === 'statue' ? 'none' : 'automatic'; +} + +function blockEntityAnimationMode(presetType) { + switch (presetType) { + case 'animated': + return 'automatic'; + case 'animated_randomly': + return 'random_idle'; + default: + return 'none'; + } +} + +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, + 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: DEFAULT_MAX_HEALTH, + movementSpeed: 0, + followRange: DEFAULT_FOLLOW_RANGE + }, + rendering: defaultRenderingSettings('cuboid_still'), + animation: defaultAnimationSettings(blockEntityAnimationMode(presetType)) + }; +} + +function presetDefaults(presetType, modelType) { + if (modelType === MODEL_TYPE_BLOCK_ENTITY) { + return blockEntityPresetDefaults(presetType); + } + + const move = movementType(presetType); + const movement = movementDefaults(presetType, move); + 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: movement.speed, + stepHeight: movement.stepHeight, + gravity: movement.gravity + }, + behavior: { + mode: mode, + lookAtPlayers: mode === 'idle_only' || mode === 'ambient', + randomStroll: wandersByMovement(move) && mode === 'ambient' + }, + attributes: { + maxHealth: DEFAULT_MAX_HEALTH, + movementSpeed: movement.speed, + followRange: DEFAULT_FOLLOW_RANGE + }, + rendering: defaultRenderingSettings(presetType), + animation: defaultAnimationSettings(animationMode(presetType)) + }; +} + +module.exports = { + SCHEMA_VERSION, + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY, + 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, + isCustom, + bodyType, + behaviorModeFor, + movementDefaults, + wandersByMovement, + animationMode, + presetDimensions, + presetShadowRadius, + presetDefaults, + defaultRenderingSettings, + defaultAnimationSettings +}; + + +/***/ }, + +/***/ 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 {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 + }); + 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: getDefaultVersionId() + }); +} + +function applyTemplate(presetType, modelType) { + return deepMerge(getDefaults(), + presetDefaults(presetType, modelType || MODEL_TYPE_ENTITY)); +} + +module.exports = { + DEFAULT_PRESET, + getDefaults, + applyTemplate, + deepMerge, + pickModelSettings +}; + + +/***/ }, + +/***/ 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', + data: {packFormat: 15}, + resource: {packFormat: 15}, + enabled: true + }, + { + id: '1.21.1', + label: 'Minecraft 1.21.1', + data: {packFormat: 48}, + resource: {packFormat: 34}, + enabled: true + }, + { + id: '1.21.11', + label: 'Minecraft 1.21.11', + 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', + data: {packFormat: 101, minFormat: [101, 1], maxFormat: [101, 1]}, + resource: {packFormat: 84, minFormat: [84, 0], maxFormat: [84, 0]}, + enabled: true + } +]; + +function getVersions() { + return VERSIONS.map((version) => ({...version})); +} + +function getVersion(id) { + return VERSIONS.find((version) => version.id === id) || null; +} + +function getDefaultVersionId() { + return DEFAULT_VERSION_ID; +} + +function getPackFormats(id) { + const version = getVersion(id); + if (!version?.enabled || !version.data || !version.resource) { + return null; + } + return { + data: version.data, + resource: version.resource + }; +} + +module.exports = { + getVersions, + 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 { + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY +} = __webpack_require__(151); +const {t} = __webpack_require__(16); +const { + presetOptions, + modelTypeOptions, + exportTypeOptions, + hostEntityTypeOptions, + movementTypeOptions, + bodyTypeOptions, + gaitOptions, + behaviorModeOptions, + animationModeOptions, + versionOptions +} = __webpack_require__(664); +const { + settingsToForm, + activeModelType, + activePreset, + presetFormValues, + resolveExportSettings +} = __webpack_require__(807); + +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; + const showServer = (form) => customizeOn(form) && standalone(form); + const showEntity = (form) => showServer(form) && !blockEntitySelected(form); + 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 + } + }; + + 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), + 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')}`}, + 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), + 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; +} + +function openExportDialog(options) { + const settings = options.settings; + const modelDimensions = options.modelDimensions; + const visibleBounds = options.visibleBounds; + 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, + visibleBounds), false); + } + } + }, + onConfirm(form) { + const finalSettings = resolveExportSettings(form, settings, + modelDimensions, visibleBounds); + finalSettings.experimental = !!options.experimental; + options.onExport(finalSettings, finalSettings.exportTarget); + } + }); + dialog.show(); + + return dialog; +} + +module.exports = { + openExportDialog +}; + + +/***/ }, + +/***/ 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, + 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}`); +} + +function entityPresetIds(experimental) { + return experimental + ? ['custom', ...SELECTABLE_PRESET_TYPES] + : SELECTABLE_PRESET_TYPES.filter(isStablePreset); +} + +function blockPresetIds(experimental) { + return experimental + ? [...BLOCK_ENTITY_PRESET_TYPES] + : BLOCK_ENTITY_PRESET_TYPES.filter(isStablePreset); +} + +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 optionsFromEnum(MOVEMENT_TYPES, 'eme.movement'); +} + +function bodyTypeOptions() { + return optionsFromEnum(BODY_TYPES, 'eme.body'); +} + +function gaitOptions() { + return optionsFromEnum(GAIT_TYPES, 'eme.gait'); +} + +function behaviorModeOptions() { + return optionsFromEnum(BEHAVIOR_MODES, 'eme.behavior'); +} + +function animationModeOptions() { + return optionsFromEnum(ANIMATION_MODES, 'eme.animation'); +} + +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 +}; + + +/***/ }, + +/***/ 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 +(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) { + } + + return base; + }; +} + +function unpatchTexturePanel() { + if (patchedVue && originalGetDescription) { + patchedVue.getDescription = originalGetDescription; + } + patchedVue = null; + originalGetDescription = null; +} + +module.exports = {patchTexturePanel, unpatchTexturePanel}; + + +/***/ }, + +/***/ 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}`; + } + + 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. + */ + +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 namespace = String(descriptor.namespace || '').trim().toLowerCase(); + 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 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`; + } + + return null; +} + +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) { + 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 + }); + } + + if (index === 0) { + if (location !== defaultLocation) { + texture = location; + } + } else { + textures[index] = location; + } + }); + + return {texture, textures, packed}; +} + +module.exports = { + resolveTextures, + parseExternalLocation, + describeTextureSource +}; + + +/***/ }, + +/***/ 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. + */ + +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}; + + +/***/ }, + +/***/ 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 {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); +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; + } + + 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(); + const visibleBounds = VisibleBounds.derive(BlockbenchAdapter.getModelCubes()); + + 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 || settings.profileId + === getDefaults().profileId)) { + 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) + }; +} + +function formatIssues(issues) { + return issues.map((issue) => `• ${issue.message}`).join('\n'); +} + +function runExport(settings, target) { + const textureResolution = resolveTextures( + BlockbenchAdapter.collectTextures(), settings); + const result = Validator.validateSettings(settings, + BlockbenchAdapter.getModelStats()); + + 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, 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(); + } + } + ); + } else { + doExport(); + } +} + +function performExport(settings, target, textureResolution) { + const options = { + modelBytes: BlockbenchAdapter.getModelBytes(), + textureResolution + }; + + BlockbenchAdapter.saveSettings(settings); + + 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 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, + visibleBounds: state.visibleBounds, + onExport: runExport + }); +} + +BBPlugin.register('easy_model_entities', { + 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.', + tags: ['Minecraft: Java Edition', 'Format', 'Exporter', 'Entity'], + version: '1.0.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 + }); + + 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 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'); + + patchTexturePanel(FORMAT_ID); + }, + onunload() { + unpatchTexturePanel(); + 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/icon.png b/plugins/easy_model_entities/icon.png new file mode 100644 index 00000000..7c6f764e Binary files /dev/null and b/plugins/easy_model_entities/icon.png differ diff --git a/plugins/easy_model_entities/members.yml b/plugins/easy_model_entities/members.yml new file mode 100644 index 00000000..bb72ac2f --- /dev/null +++ b/plugins/easy_model_entities/members.yml @@ -0,0 +1,2 @@ +maintainers: + - MarkusBordihn diff --git a/plugins/easy_model_entities/src/BlockbenchAdapter.js b/plugins/easy_model_entities/src/BlockbenchAdapter.js new file mode 100644 index 00000000..f2309783 --- /dev/null +++ b/plugins/easy_model_entities/src/BlockbenchAdapter.js @@ -0,0 +1,290 @@ +/* + * 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'); + +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; + } + + static #firstTexture() { + if (typeof Texture === 'undefined') { + return 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 collectTextures() { + if (typeof Texture === 'undefined' || !Texture.all) { + return []; + } + + 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 #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 : []; + const animations = + typeof Animation !== 'undefined' && Animation.all ? Animation.all : []; + const texture = BlockbenchAdapter.#firstTexture(); + + 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, + animationCount: animations.length, + animations: animations.map( + (animation) => BlockbenchAdapter.#summarizeAnimation(animation)), + 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 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 ''; + } + + 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 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'}); + } + + 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( + { + 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 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 = + 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..a6172f14 --- /dev/null +++ b/plugins/easy_model_entities/src/builders/exporter.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 {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 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; + const modelType = settings.modelType || 'entity'; + + return { + profile: `data/${namespace}/easy_model_entities/profiles/${modelType}/${id}.json`, + renderProfile: + `assets/${namespace}/easy_model_entities/render_profiles/${modelType}/${id}.json`, + model: `assets/${namespace}/easy_model_entities/models/${id}.bbmodel` + }; +} + +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) + ]; +} + +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 = []; + const serverProfile = settings.modelOnly ? null : buildServerProfile( + settings); + stampVersion(serverProfile, renderProfile); + if (serverProfile) { + 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}, + ...textureFiles(settings, opts.textureResolution)); + + return {files, serverProfile, renderProfile}; +} + +module.exports = { + buildPackBundle, + buildModProjectFiles +}; 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..593eb914 --- /dev/null +++ b/plugins/easy_model_entities/src/builders/packMeta.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 {getPackFormats} = require('../model/versionMatrix'); + +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 buildPack(formats[kind], description); +} + +function buildDataPackMcmeta(settings) { + return buildMcmeta(settings, 'data', + 'Easy Model Entities server profiles'); +} + +function buildResourcePackMcmeta(settings) { + return buildMcmeta(settings, 'resource', + '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..ce635917 --- /dev/null +++ b/plugins/easy_model_entities/src/builders/profileDiff.js @@ -0,0 +1,49 @@ +/* + * 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 valuesDiffer(a, b) { + if (Array.isArray(a) || Array.isArray(b)) { + return JSON.stringify(a) !== JSON.stringify(b); + } + + return a !== b; +} + +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; +} + +function assignIfPresent(target, key, section) { + if (!isEmpty(section)) { + target[key] = section; + } +} + +module.exports = {diffFlat, 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..f3b71d6a --- /dev/null +++ b/plugins/easy_model_entities/src/builders/readme.js @@ -0,0 +1,44 @@ +/* + * 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, 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}; 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..b1c108c5 --- /dev/null +++ b/plugins/easy_model_entities/src/builders/renderProfile.js @@ -0,0 +1,106 @@ +/* + * 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, + defaultRenderingSettings, + defaultAnimationSettings, + isCustom, + MODEL_TYPE_BLOCK_ENTITY +} = require('../model/presetTypes'); +const {diffFlat, assignIfPresent} = require('./profileDiff'); + +function renderPresetType(settings) { + return settings.modelType === MODEL_TYPE_BLOCK_ENTITY + ? '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) { + 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 defaults = defaultAnimationSettings( + animationMode(renderPresetType(settings))); + const animation = diffFlat(toAnimationJson(settings.animation), + toAnimationJson(defaults)); + + if (settings.animation.mode === 'none') { + delete animation.swing_speed; + delete animation.walk_speed_multiplier; + delete animation.idle_strength; + delete animation.gait; + } + + return animation; +} + +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 + }; + + if (custom || settings.host.bodyType !== bodyType(renderPreset)) { + profile.body_type = settings.host.bodyType; + } + + profile.model = + `${settings.namespace}:easy_model_entities/models/${settings.profileId}`; + + 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 +}; 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..f0c7202b --- /dev/null +++ b/plugins/easy_model_entities/src/builders/serverProfile.js @@ -0,0 +1,135 @@ +/* + * 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, + wandersByMovement, + isCustom, + DEFAULT_MAX_HEALTH, + DEFAULT_FOLLOW_RANGE, + MODEL_TYPE_BLOCK_ENTITY, + MODEL_TYPE_ENTITY +} = require('../model/presetTypes'); +const {diffFlat, assignIfPresent} = require('./profileDiff'); + +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: wandersByMovement(movementType) && 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 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 (!blockEntity) { + assignIfPresent(profile, 'entity', buildEntity(settings, custom)); + } + + 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; +} + +module.exports = {buildServerProfile}; 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..2f16cebf --- /dev/null +++ b/plugins/easy_model_entities/src/format/EmeCodecHooks.js @@ -0,0 +1,59 @@ +/* + * 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 {pickModelSettings} = require('../model/templates'); +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] = pickModelSettings(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..ca7d2f1d --- /dev/null +++ b/plugins/easy_model_entities/src/format/EmeFormat.js @@ -0,0 +1,71 @@ +/* + * 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, + texture_folder: true, + 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); + } + } + }); +} + +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 new file mode 100644 index 00000000..33a7d671 --- /dev/null +++ b/plugins/easy_model_entities/src/i18n/translations.js @@ -0,0 +1,241 @@ +/* + * 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 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.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', + '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.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)', + '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.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', + '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.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)', + '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.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', + '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.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)', + '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.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', + '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.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)', + '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); + } +} + +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..c32684d3 --- /dev/null +++ b/plugins/easy_model_entities/src/index.js @@ -0,0 +1,336 @@ +/* + * 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 {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'); +const {openExportDialog} = require('./ui/exportDialog'); +const {registerTranslations, t} = require('./i18n/translations'); +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 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; + } + + 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(); + const visibleBounds = VisibleBounds.derive(BlockbenchAdapter.getModelCubes()); + + 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 || settings.profileId + === getDefaults().profileId)) { + 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) + }; +} + +function formatIssues(issues) { + return issues.map((issue) => `• ${issue.message}`).join('\n'); +} + +function runExport(settings, target) { + const textureResolution = resolveTextures( + BlockbenchAdapter.collectTextures(), settings); + const result = Validator.validateSettings(settings, + BlockbenchAdapter.getModelStats()); + + 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, 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(); + } + } + ); + } else { + doExport(); + } +} + +function performExport(settings, target, textureResolution) { + const options = { + modelBytes: BlockbenchAdapter.getModelBytes(), + textureResolution + }; + + BlockbenchAdapter.saveSettings(settings); + + 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 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, + visibleBounds: state.visibleBounds, + onExport: runExport + }); +} + +BBPlugin.register('easy_model_entities', { + 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.', + tags: ['Minecraft: Java Edition', 'Format', 'Exporter', 'Entity'], + version: '1.0.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 + }); + + 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 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'); + + patchTexturePanel(FORMAT_ID); + }, + onunload() { + unpatchTexturePanel(); + unregisterEmeCodecHooks(); + unregisterEmeFormat(); + 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..d896c6f6 --- /dev/null +++ b/plugins/easy_model_entities/src/model/ModelDimensions.js @@ -0,0 +1,81 @@ +/* + * 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) { + return Math.round(value * 1000) / 1000; + } + + 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}; + } + + 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; + + if (width <= 0 || height <= 0) { + return {...ModelDimensions.FALLBACK}; + } + + 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) + }; + } + + static applyModelDimensions(settings, modelDimensions) { + if (!modelDimensions) { + return settings; + } + + settings.dimensions.width = modelDimensions.width; + settings.dimensions.height = modelDimensions.height; + settings.dimensions.eyeHeight = modelDimensions.eyeHeight; + 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..46a81c32 --- /dev/null +++ b/plugins/easy_model_entities/src/model/PresetDetector.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 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; + } + + 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), + z: Math.max(0, bounds.maxZ - bounds.minZ) + }; + } + + 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) { + 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'}; + } + } + + 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..bc10fb62 --- /dev/null +++ b/plugins/easy_model_entities/src/model/Validator.js @@ -0,0 +1,308 @@ +/* + * 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, + ANIMATION_CLIPS, + MODEL_TYPE_BLOCK_ENTITY +} = require('./presetTypes'); + +const SUPPORTED_ANIMATION_CHANNELS = new Set(['rotation', 'position']); + +class Validator { + static BUDGETS = { + maxTextureSize: 2048, + softTextureSize: 128, + maxModelFileSizeBytes: 2 * 1024 * 1024, + softModelFileSizeBytes: 1024 * 1024, + maxCubeCount: 512, + softCubeCount: 384, + maxBoneCount: 128, + softBoneCount: 96, + maxHierarchyDepth: 32, + softHierarchyDepth: 24, + maxAnimationCount: 16 + }; + + 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 #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 = []; + 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; + 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.textureWidth) + && Validator.#isFiniteNumber(ctx.textureHeight)) { + 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` + }); + 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) { + errors.push({ + code: 'HIGH_ANIMATION_COUNT', + 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({ + code: 'MISSING_BODY_PART', + message: `Missing recommended body part: ${part}` + }); + }); + + 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/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 new file mode 100644 index 00000000..44175d4f --- /dev/null +++ b/plugins/easy_model_entities/src/model/presetTypes.js @@ -0,0 +1,419 @@ +/* + * 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 = '0.2.0'; + +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 ANIMATION_CLIPS = ['idle', 'walk', 'swim', 'fly', 'hurt', 'death', + 'attack']; +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}; + +const DEFAULT_MAX_HEALTH = 10; +const DEFAULT_FOLLOW_RANGE = 16; + +const PRESET_TYPES = [ + 'custom', + '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' +]; + +const BLOCK_ENTITY_PRESET_TYPES = [ + 'static', + 'ticking', + 'animated', + 'animated_randomly' +]; + +const STABLE_PRESET_TYPES = new Set([ + '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', + // Block entity stable presets. + 'animated', + 'animated_randomly' +]); + +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 'amphibious_still': + case 'amphibious_wandering': + return 'amphibious'; + 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 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; +} + +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 '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}; + 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}; + } +} + +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': + 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 behaviorModeFor(presetType, move) { + if (isMoving(presetType)) { + return 'ambient'; + } + + if (isStill(presetType)) { + return 'idle_only'; + } + + if (presetType === 'custom') { + return move === 'ground' || move === 'amphibious' ? 'idle_only' : 'static'; + } + + return 'static'; +} + +function behaviorMode(presetType) { + return behaviorModeFor(presetType, movementType(presetType)); +} + +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) { + return { + speed: defaultSpeed(move), + stepHeight: move === 'ground' || move === 'amphibious' ? 0.6 : 0, + gravity: presetType === 'static' || move !== 'static' + }; +} + +function animationMode(presetType) { + return presetType === 'custom' || presetType === 'static' + || presetType === 'statue' ? 'none' : 'automatic'; +} + +function blockEntityAnimationMode(presetType) { + switch (presetType) { + case 'animated': + return 'automatic'; + case 'animated_randomly': + return 'random_idle'; + default: + return 'none'; + } +} + +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, + 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: DEFAULT_MAX_HEALTH, + movementSpeed: 0, + followRange: DEFAULT_FOLLOW_RANGE + }, + rendering: defaultRenderingSettings('cuboid_still'), + animation: defaultAnimationSettings(blockEntityAnimationMode(presetType)) + }; +} + +function presetDefaults(presetType, modelType) { + if (modelType === MODEL_TYPE_BLOCK_ENTITY) { + return blockEntityPresetDefaults(presetType); + } + + const move = movementType(presetType); + const movement = movementDefaults(presetType, move); + 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: movement.speed, + stepHeight: movement.stepHeight, + gravity: movement.gravity + }, + behavior: { + mode: mode, + lookAtPlayers: mode === 'idle_only' || mode === 'ambient', + randomStroll: wandersByMovement(move) && mode === 'ambient' + }, + attributes: { + maxHealth: DEFAULT_MAX_HEALTH, + movementSpeed: movement.speed, + followRange: DEFAULT_FOLLOW_RANGE + }, + rendering: defaultRenderingSettings(presetType), + animation: defaultAnimationSettings(animationMode(presetType)) + }; +} + +module.exports = { + SCHEMA_VERSION, + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY, + 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, + isCustom, + bodyType, + behaviorModeFor, + movementDefaults, + wandersByMovement, + animationMode, + presetDimensions, + presetShadowRadius, + presetDefaults, + defaultRenderingSettings, + defaultAnimationSettings +}; 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..1c2af1df --- /dev/null +++ b/plugins/easy_model_entities/src/model/templates.js @@ -0,0 +1,94 @@ +/* + * 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 {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 + }); + 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: getDefaultVersionId() + }); +} + +function applyTemplate(presetType, modelType) { + return deepMerge(getDefaults(), + presetDefaults(presetType, modelType || MODEL_TYPE_ENTITY)); +} + +module.exports = { + DEFAULT_PRESET, + getDefaults, + applyTemplate, + deepMerge, + pickModelSettings +}; 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..ab180c50 --- /dev/null +++ b/plugins/easy_model_entities/src/model/versionMatrix.js @@ -0,0 +1,80 @@ +/* + * 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', + data: {packFormat: 15}, + resource: {packFormat: 15}, + enabled: true + }, + { + id: '1.21.1', + label: 'Minecraft 1.21.1', + data: {packFormat: 48}, + resource: {packFormat: 34}, + enabled: true + }, + { + id: '1.21.11', + label: 'Minecraft 1.21.11', + 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', + data: {packFormat: 101, minFormat: [101, 1], maxFormat: [101, 1]}, + resource: {packFormat: 84, minFormat: [84, 0], maxFormat: [84, 0]}, + enabled: true + } +]; + +function getVersions() { + return VERSIONS.map((version) => ({...version})); +} + +function getVersion(id) { + return VERSIONS.find((version) => version.id === id) || null; +} + +function getDefaultVersionId() { + return DEFAULT_VERSION_ID; +} + +function getPackFormats(id) { + const version = getVersion(id); + if (!version?.enabled || !version.data || !version.resource) { + return null; + } + return { + data: version.data, + resource: version.resource + }; +} + +module.exports = { + getVersions, + 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..9ffd6cb1 --- /dev/null +++ b/plugins/easy_model_entities/src/package-lock.json @@ -0,0 +1,5790 @@ +{ + "name": "easy_model_entities", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "easy_model_entities", + "version": "1.0.0", + "devDependencies": { + "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": { + "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/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", + "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-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", + "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": "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": ">=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": "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": "^3.0.0", + "graceful-fs": "^4.2.11", + "progress": "^2.0.3", + "semver": "^7.6.3", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=22.12.0" + }, + "optionalDependencies": { + "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": { + "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": "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": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/core": { + "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": "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.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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment": { + "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": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-mock": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect": { + "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": "30.4.1", + "jest-snapshot": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "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": "30.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "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": "30.4.1", + "@sinonjs/fake-timers": "^15.4.0", + "@types/node": "*", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" + }, + "engines": { + "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": "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": { + "@types/node": "*", + "jest-regex-util": "30.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/reporters": { + "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": "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.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": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "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" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "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": { + "@jest/types": "30.4.1", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/source-map": { + "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.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-result": { + "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": "30.4.1", + "@jest/types": "30.4.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "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": "30.4.1", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/transform": { + "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.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.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": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/types": { + "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/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.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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/@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": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "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", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "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": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "dev": true, + "license": "MIT" + }, + "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": "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.1" + } + }, + "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": { + "tslib": "^2.4.0" + } + }, + "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/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/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/node": { + "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": { + "undici-types": "~7.18.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/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/@ungap/structured-clone": { + "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" + }, + "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, + "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", + "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", + "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", + "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", + "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", + "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" + } + }, + "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/@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": "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": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "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/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", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/babel-jest": { + "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": "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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-0" + } + }, + "node_modules/babel-plugin-istanbul": { + "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.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "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": { + "@types/babel__core": "^7.20.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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": "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": "30.4.0", + "babel-preset-current-node-syntax": "^1.2.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + } + }, + "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.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": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/blockbench-types": { + "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", + "@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": "^40.8.0", + "prismjs": "^1.29.0", + "tinycolor2": "^1.6.0", + "typescript": "^5.8.3", + "vue": "2.7.14", + "wintersky": "^1.3.0" + } + }, + "node_modules/brace-expansion": { + "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": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", + "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.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", + "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-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/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.30001805", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001805.tgz", + "integrity": "sha512-52noaS3DubycKSXaU30TwPGIp+POyQSUVa5jBEq3vkRkY0kjyb3LQgvhU6WGyCcyXqVLWO0Cw0Q6BSdD0kUfVA==", + "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": "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/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, + "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/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", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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": "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/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/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/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": "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": { + "glob-parent": "^6.0.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^7.0.3", + "tinyglobby": "^0.2.12" + }, + "engines": { + "node": ">= 20.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.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/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/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/dompurify": { + "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": { + "@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": "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", + "dependencies": { + "@electron-internal/extract-zip": "^1.0.1", + "@electron/get": "^5.0.0", + "@types/node": "^24.9.0" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 22.12.0" + } + }, + "node_modules/electron-to-chromium": { + "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" + }, + "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": "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/enhanced-resolve": { + "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": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/env-paths": { + "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": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-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.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" + }, + "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": "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/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/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/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": "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": "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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "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-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.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "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/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", + "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/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": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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": "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/glob": { + "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": { + "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" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "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/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/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/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/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-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-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": "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" + }, + "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/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": "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": "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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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": "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.1.1", + "jest-util": "30.4.1", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-circus": { + "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": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "co": "^4.6.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": "30.4.1", + "pure-rand": "^7.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-cli": { + "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": "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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "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": "30.4.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "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-diff": { + "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": { + "@jest/diff-sequences": "30.4.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-docblock": { + "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.1.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-each": { + "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/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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-environment-node": { + "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": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "jest-mock": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-haste-map": { + "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": "30.4.1", + "@types/node": "*", + "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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-leak-detector": { + "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": "30.1.0", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "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": { + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util": { + "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.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.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-mock": { + "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": "30.4.1", + "@types/node": "*", + "jest-util": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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": "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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve": { + "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.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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "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": "30.4.0", + "jest-snapshot": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runner": { + "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": "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.1.2", + "emittery": "^0.13.1", + "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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-runtime": { + "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": "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.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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-snapshot": { + "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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util": { + "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": "30.4.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-validate": { + "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/get-type": "30.1.0", + "@jest/types": "30.4.1", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", + "leven": "^3.1.0", + "pretty-format": "30.4.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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": "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": "30.4.1", + "@jest/types": "30.4.1", + "@types/node": "*", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "emittery": "^0.13.1", + "jest-util": "30.4.1", + "string-length": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-worker": { + "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": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.4.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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.15.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.0.tgz", + "integrity": "sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==", + "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-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/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/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/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/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/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/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/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/minimatch": { + "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": "^2.0.2" + }, + "engines": { + "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": { + "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.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "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/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", + "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.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", + "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/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/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-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/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", + "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-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": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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": "ISC" + }, + "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": "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": { + "node": ">=12" + }, + "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.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "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": "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": "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": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.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/pure-rand": { + "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": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "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", + "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-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/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": "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" + } + }, + "node_modules/serialize-javascript": { + "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": { + "node": ">=20.0.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": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/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/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-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==", + "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/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" + }, + "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" + } + }, + "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/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", + "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.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": { + "@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/test-exclude/node_modules/brace-expansion": { + "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": { + "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", + "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/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/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/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": "0BSD", + "optional": true + }, + "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": "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": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "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": "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/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", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "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": { + "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": "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": "^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": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.101.0", + "webpack-bundle-analyzer": "^4.0.0 || ^5.0.0", + "webpack-dev-server": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "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": ">=20" + } + }, + "node_modules/webpack-merge": { + "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.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "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": { + "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": "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==", + "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/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", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "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": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.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.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "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/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": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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..0c1093e2 --- /dev/null +++ b/plugins/easy_model_entities/src/package.json @@ -0,0 +1,28 @@ +{ + "name": "easy_model_entities", + "version": "1.0.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": "^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", + "testMatch": [ + "**/tests/**/*.test.js" + ], + "transform": { + "\\.md$": "/tests/mdTransform.js" + } + } +} 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/icon.png b/plugins/easy_model_entities/src/resources/icon.png new file mode 100644 index 00000000..7c6f764e Binary files /dev/null and b/plugins/easy_model_entities/src/resources/icon.png differ diff --git a/plugins/easy_model_entities/src/resources/readme.md b/plugins/easy_model_entities/src/resources/readme.md new file mode 100644 index 00000000..bdf079b6 --- /dev/null +++ b/plugins/easy_model_entities/src/resources/readme.md @@ -0,0 +1,61 @@ +# {{id}} - Easy Model Entities pack + +Generated by the **Easy Model Entities Exporter** Blockbench plugin for the **Easy Model Entities** +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. Their file names are prefixed +with the model id so several exported packs can live in the same folder without overwriting each +other: + +- `{{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: + +- [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 + +You do not need to unpack anything — just move the two ZIP files into the right folders. + +### 1. Resource Pack (visuals, client side) + +Move `{{resourcepackFile}}` 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 `{{datapackFile}}` 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/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 new file mode 100644 index 00000000..240e903c --- /dev/null +++ b/plugins/easy_model_entities/src/tests/contractSync.test.js @@ -0,0 +1,122 @@ +/* + * 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 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'); +const { + SCHEMA_VERSION, + MODEL_TYPES, + BODY_TYPES, + MOVEMENT_TYPES, + BEHAVIOR_MODES, + PRESET_TYPES, + BLOCK_ENTITY_PRESET_TYPES, + ANIMATION_MODES, + ANIMATION_CLIPS, + GAIT_TYPES +} = require('../model/presetTypes'); +const {applyTemplate} = require('../model/templates'); + +function sorted(values) { + return values.slice().sort(); +} + +describe('mod API contract sync', () => { + 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(ANIMATION_CLIPS)).toEqual( + sorted(contract.enums.animation_clips)); + 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.2.0', + model_type: 'entity', + preset_type: 'humanoid_wandering' + }); + expect(buildRenderProfile(settings)).toEqual({ + schema_version: '0.2.0', + preset_type: 'humanoid_wandering', + model: + 'easy_model_entities_examples:easy_model_entities/models/little_explorer' + }); + }); + + 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.2.0', + model_type: 'block_entity', + preset_type: 'animated_randomly' + }); + expect(buildRenderProfile(settings)).toEqual({ + 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/exportDialog.test.js b/plugins/easy_model_entities/src/tests/exportDialog.test.js new file mode 100644 index 00000000..d961a40e --- /dev/null +++ b/plugins/easy_model_entities/src/tests/exportDialog.test.js @@ -0,0 +1,198 @@ +/* + * 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, + presetFormValues, + resolveExportSettings +} = require('../ui/exportSettingsMapping'); +const {presetOptions} = require('../ui/exportDialogOptions'); +const {fixtureSettings} = require('./fixtureData'); + +const MODEL_DIMS = {width: 1, height: 2, eyeHeight: 1.8}; + +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, 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 on top of every selectable preset', + () => { + const options = presetOptions('entity', true); + expect(Object.keys(options)[0]).toBe('custom'); + expect(Object.keys(options)).toHaveLength(20); + expect(options.amphibious_wandering).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.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', + 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'); + }); + + 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..34e9e9b9 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/exporter.test.js @@ -0,0 +1,244 @@ +/* + * 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) { + 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/entity/lizard.json', + 'assets/example/textures/entity/lizard.png', + 'pack.mcmeta' + ].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'); + 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, 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/entity/lizard.json') + .content); + expect(server.model_type).toBe('entity'); + expect(server.preset_type).toBe('quadruped_wandering'); + 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(); + 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.textureResolution.packed[0].bytes); + }); +}); + +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/entity/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 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/entity/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); + }); + + 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/entity/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', () => { + function blockSettings() { + const settings = applyTemplate('animated_randomly', 'block_entity'); + settings.namespace = 'example'; + settings.profileId = 'shrine'; + return settings; + } + + 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/fixtureData.js b/plugins/easy_model_entities/src/tests/fixtureData.js new file mode 100644 index 00000000..34f063a3 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/fixtureData.js @@ -0,0 +1,56 @@ +/* + * 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 {resolveTextures} = require('../utils/TextureResolver'); + +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 fixtureTextureResolution(settings) { + return resolveTextures([ + { + index: 0, name: '', namespace: '', folder: '', path: '', + bytes: FIXTURE_TEXTURE_BYTES + } + ], settings || fixtureSettings()); +} + +function fixtureExportOptions(settings) { + return { + modelBytes: FIXTURE_MODEL_BYTES, + textureResolution: fixtureTextureResolution(settings) + }; +} + +module.exports = { + FIXTURE_MODEL_BYTES, + FIXTURE_TEXTURE_BYTES, + fixtureSettings, + fixtureExportOptions +}; 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 new file mode 100644 index 00000000..cbe3b917 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/fixtures/easy-model-api-0.2.0.json @@ -0,0 +1,127 @@ +{ + "schema_version": "0.2.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" + ], + "animation_clips": [ + "idle", + "walk", + "swim", + "fly", + "hurt", + "death", + "attack" + ], + "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/mdTransform.js b/plugins/easy_model_entities/src/tests/mdTransform.js new file mode 100644 index 00000000..ad8b4f40 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/mdTransform.js @@ -0,0 +1,24 @@ +/* + * 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/modUpdate.test.js b/plugins/easy_model_entities/src/tests/modUpdate.test.js new file mode 100644 index 00000000..fdb5a853 --- /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.2.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/modelDimensions.test.js b/plugins/easy_model_entities/src/tests/modelDimensions.test.js new file mode 100644 index 00000000..d050b549 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/modelDimensions.test.js @@ -0,0 +1,71 @@ +/* + * 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', () => { + 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) + }); + + 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); + }); +}); + +describe('applyModelDimensions', () => { + const dims = {width: 1, height: 2, eyeHeight: 1.8}; + + function settings() { + return {dimensions: {width: 0, height: 0, eyeHeight: 0}}; + } + + test('overlays dimensions', () => { + const result = ModelDimensions.applyModelDimensions(settings(), dims); + expect(result.dimensions).toEqual({width: 1, height: 2, eyeHeight: 1.8}); + }); + + 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/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/presetDetector.test.js b/plugins/easy_model_entities/src/tests/presetDetector.test.js new file mode 100644 index 00000000..1c8dfa02 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/presetDetector.test.js @@ -0,0 +1,89 @@ +/* + * 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) { + 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..f70fd3b7 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/readme.test.js @@ -0,0 +1,60 @@ +/* + * 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'); + +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('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'); + }); + + 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..dd784de1 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/resourceLocation.test.js @@ -0,0 +1,76 @@ +/* + * 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', () => { + 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..543d673e --- /dev/null +++ b/plugins/easy_model_entities/src/tests/schemaConsistency.test.js @@ -0,0 +1,129 @@ +/* + * 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} = require('../model/templates'); +const {SELECTABLE_PRESET_TYPES} = require('../model/presetTypes'); +const {buildServerProfile} = require('../builders/serverProfile'); +const {buildRenderProfile} = require('../builders/renderProfile'); + +function withId(settings) { + settings.namespace = 'example'; + settings.profileId = 'lizard'; + return settings; +} + +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 only', () => { + expect(buildServerProfile(settings)).toEqual({ + schema_version: '0.2.0', + model_type: 'entity', + 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' + }); + }); + }); +}); + +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 and no entity behavior', + () => { + const profile = buildServerProfile(blockSettings('animated_randomly')); + expect(profile).toEqual({ + schema_version: '0.2.0', + model_type: 'block_entity', + preset_type: 'animated_randomly' + }); + 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')), { + 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.2.0', + model_type: 'entity', + preset_type: 'quadruped_wandering', + 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..c54dbf31 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/templates.test.js @@ -0,0 +1,117 @@ +/* + * 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, + DEFAULT_PRESET, + deepMerge +} = require('../model/templates'); +const { + presetDefaults, + PRESET_TYPES, + SELECTABLE_PRESET_TYPES +} = require('../model/presetTypes'); + +describe('templates', () => { + 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(19); + }); + + 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 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..36f5a78b --- /dev/null +++ b/plugins/easy_model_entities/src/tests/textureResolver.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 { + parseExternalLocation, + resolveTextures, + describeTextureSource +} = 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(); + }); + + 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', () => { + 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('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([ + { + 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 new file mode 100644 index 00000000..c484caf4 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/validation.test.js @@ -0,0 +1,225 @@ +/* + * 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'); + +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('allows multiple textures without warning', () => { + const result = Validator.validateSettings(fixtureSettings(), { + hasModel: true, + hasTexture: true, + boneNames: QUADRUPED_BONES + }); + expect(result.valid).toBe(true); + expect(codes(result.warnings)).not.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 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('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: 'dance', 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']}); + 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..091437c9 --- /dev/null +++ b/plugins/easy_model_entities/src/tests/versionMatrix.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 { + getVersions, + getDefaultVersionId, + getPackFormats +} = require('../model/versionMatrix'); + +describe('versionMatrix', () => { + test('default version is 1.20.1; all four versions are enabled', () => { + expect(getDefaultVersionId()).toBe('1.20.1'); + 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']); + }); + + 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('legacy versions use a single integer pack_format', () => { + expect(getPackFormats('1.20.1')).toEqual( + {data: {packFormat: 15}, resource: {packFormat: 15}}); + expect(getPackFormats('1.21.1')).toEqual( + {data: {packFormat: 48}, resource: {packFormat: 34}}); + }); + + 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 new file mode 100644 index 00000000..c9fe39ab --- /dev/null +++ b/plugins/easy_model_entities/src/ui/exportDialog.js @@ -0,0 +1,327 @@ +/* + * 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 { + MODEL_TYPE_ENTITY, + MODEL_TYPE_BLOCK_ENTITY +} = require('../model/presetTypes'); +const {t} = require('../i18n/translations'); +const { + presetOptions, + modelTypeOptions, + exportTypeOptions, + hostEntityTypeOptions, + movementTypeOptions, + bodyTypeOptions, + gaitOptions, + behaviorModeOptions, + animationModeOptions, + versionOptions +} = require('./exportDialogOptions'); +const { + settingsToForm, + activeModelType, + activePreset, + presetFormValues, + resolveExportSettings +} = require('./exportSettingsMapping'); + +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; + const showServer = (form) => customizeOn(form) && standalone(form); + const showEntity = (form) => showServer(form) && !blockEntitySelected(form); + 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 + } + }; + + 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), + 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')}`}, + 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), + 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; +} + +function openExportDialog(options) { + const settings = options.settings; + const modelDimensions = options.modelDimensions; + const visibleBounds = options.visibleBounds; + 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, + visibleBounds), false); + } + } + }, + onConfirm(form) { + const finalSettings = resolveExportSettings(form, settings, + modelDimensions, visibleBounds); + finalSettings.experimental = !!options.experimental; + options.onExport(finalSettings, finalSettings.exportTarget); + } + }); + dialog.show(); + + return dialog; +} + +module.exports = { + openExportDialog +}; 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..d4945bf5 --- /dev/null +++ b/plugins/easy_model_entities/src/ui/exportDialogOptions.js @@ -0,0 +1,139 @@ +/* + * 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, + 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}`); +} + +function entityPresetIds(experimental) { + return experimental + ? ['custom', ...SELECTABLE_PRESET_TYPES] + : SELECTABLE_PRESET_TYPES.filter(isStablePreset); +} + +function blockPresetIds(experimental) { + return experimental + ? [...BLOCK_ENTITY_PRESET_TYPES] + : BLOCK_ENTITY_PRESET_TYPES.filter(isStablePreset); +} + +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 optionsFromEnum(MOVEMENT_TYPES, 'eme.movement'); +} + +function bodyTypeOptions() { + return optionsFromEnum(BODY_TYPES, 'eme.body'); +} + +function gaitOptions() { + return optionsFromEnum(GAIT_TYPES, 'eme.gait'); +} + +function behaviorModeOptions() { + return optionsFromEnum(BEHAVIOR_MODES, 'eme.behavior'); +} + +function animationModeOptions() { + return optionsFromEnum(ANIMATION_MODES, 'eme.animation'); +} + +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/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 +}; 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..1d3a90fc --- /dev/null +++ b/plugins/easy_model_entities/src/ui/texturePanel.js @@ -0,0 +1,71 @@ +/* + * 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) { + } + + 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/ResourceLocation.js b/plugins/easy_model_entities/src/utils/ResourceLocation.js new file mode 100644 index 00000000..db01f06e --- /dev/null +++ b/plugins/easy_model_entities/src/utils/ResourceLocation.js @@ -0,0 +1,69 @@ +/* + * 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}`; + } + + 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/utils/TextureResolver.js b/plugins/easy_model_entities/src/utils/TextureResolver.js new file mode 100644 index 00000000..93e37ef5 --- /dev/null +++ b/plugins/easy_model_entities/src/utils/TextureResolver.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 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 namespace = String(descriptor.namespace || '').trim().toLowerCase(); + 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 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`; + } + + return null; +} + +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) { + 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 + }); + } + + if (index === 0) { + if (location !== defaultLocation) { + texture = location; + } + } else { + textures[index] = location; + } + }); + + return {texture, textures, packed}; +} + +module.exports = { + resolveTextures, + parseExternalLocation, + describeTextureSource +}; 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..a3e4d48d --- /dev/null +++ b/plugins/easy_model_entities/src/utils/hash.js @@ -0,0 +1,31 @@ +/* + * 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 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}; 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 + } +};